题目一
1、数组中,如果有一种数,出现次数大于一半,则打印该数。
2、一个数组,数组长度为N。输入数字K,输出所有出现次数大于N/K的数。
思路:
第一题:
从前向后遍历数组。
设置两个变量,一个变量保存候选者cand,一个存该候选者的血量hp=0。
- 如果当前hp=0,说明此时cand没有保存任何人,将当前遍历的人保存起来;
- 否则将当前元素与cand保存的元素进行比较
- 如果当前元素与cand保存的值相同的,则hp++,否则hp--;
遍历到数组的结尾时,将cand保存的值在数组中进行校验(因为对于数组1,2,3,4,5,cand最后的值是5)
void printHalfMajor(vector<int>&num){
if(!num.size()) return;
int cand=0, hp=0;
for(int i=0; i!=num.size(); i++){
if(!hp){
cand=num[i];
hp++;
}else{
if(cand==num[i]) hp++;
else hp--;
}
}
if(!hp) return;
hp=0;
//验证cand是不是真的过半
for(int i=0; i<num.size(); i++){
if(num[i]==cand) hp++;
}
if(hp>num.size()/2)
cout<<cand<<endl;
else
cout<<"no such number!"<<endl;
}
第二题:
出现次数大于N/K的才打印,最多是k-1组,因为N是数组长度。
利用数组map存放候选人和他的血量。map的尺寸不超过k-1.从前向后遍历数组,如果该值在map中存在,则血量++,不存在,如果map尺寸不到k-1,则将该值加入到map中,否则将map中每个元素减少一个血量。
void printKMajor(vector<int>num, int k){
unordered_map<int, int>hash;
//先看存在不存在,如果不存在,再看满不满
for(auto i : num){
//存在,创建键值对
if(hash.find(i)!=hash.end()){
hash[i]++;
}else{
//最多k组,不可能有超过N/K的组超过k
if(hash.size()<k){
hash[i]=1;
//满了且不存在i
}else{
//删除hash中每一个元素的一点血量
for(auto i : hash){
//血量大于1,则减血
if(i.second>1) i.second--;
//血量等于1,则去掉该元素
else hash.erase(i.first);
}
}
}
}
//验证每个值的血量
//清空血量
for(auto i : hash)
i.second=0;
//计算每个cand的出现次数
for(auto i :num)
if(hash.find(i)!=hash.end())
hash[i]++;
//判断每个cand的出现次数是不是大于N/k
for(auto i : hash){
if(i.second<=num.size()/k)
hash.erase(i.first);
else
cout<<i.first<<endl;
}
}
题目二
数组中某一段上,给你一个值k表示该位置左侧元素的个数。请将左侧部分彻底填到右边去。
比如数组abcdef,
- 如果k=3,则变换之后数组的值为 defabc
- 如果k=2,变换之后数组的值为 cdefab
要求:空间复杂度O(1),时间复杂度O(n)
思路
1、在某一段逆序的方法——对于数组abcd进行逆序的过程:
- a和d交换
- b和c交换
即两头和两头交换,直到中间为止。此时不需要额外空间,时间复杂度是O(n)。
2、对于本题,可以先左侧部分逆序,再右侧部分逆序,最后整体逆序。
对于数组,左侧长度为L,右侧长度为R,数组长度为N。则交换的次数为L/2+R/2+(L+R)/2=N。
更好的办法——减少交换次数
比如abcdefg,左侧长度为4,右侧长度为4,那么不用交换8次,直接a与e交换,b与f交换,c与g交换,d与h交换即可。
说明上面的方法有优化空间。
对于abcdefgh,左侧长度为2,右侧长度为6,则交换过程如下:
1、由于已知ab一定会移动到最右侧,则先将ab和gh交换。交换之后ab的位置固定。左侧变成gh,右侧是cedf
2、由于此时还是左侧小,因为gh个ef交换
3、当两侧长度相同时,说明此次交换是最后一次。
实现代码
/*
先将左侧逆序,再将右侧逆序,然后整体逆序。
原理:
将一个数组逆序,就是把开头和最后一个元素交换,第二个元素和倒数第二个元素交换,以此类推。
可以利用两根指针来进行此操作,当两根指针相遇时,交换结束。
*/
#include<iostream>
#include<vector>
using namespace std;
int createNum(){
/*
要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;
*/
//生成2到9之间的随机数
return rand()%8+2;
}
void swap(int begin, int end, vector<int>&num){
int temp=num[begin];
num[begin]=num[end];
num[end]=temp;
}
void change(vector<int>&num, int begin, int end){
while(end-begin>=1){
swap(begin,end,num);
begin++;
end--;
}
}
void Rotate(vector<int>&num, int k){
if(!num.size()) return;
//交换左侧
int begin=0, end=k-1;
change(num,begin, end);
//交换右侧
begin=k, end=num.size()-1;
change(num,begin, end);
//交换全局
begin=0, end=num.size()-1;
change(num,begin, end);
for(auto i : num)
cout<<i<<endl;
}
int main(){
vector<int>num={1,2,3,4,5,6,7,8,9,10};
int k=createNum();
cout<<"k值为:"<<k<<endl;
Rotate(num, k);
return 0;
}
题目三
假设s和m初始化,s = "a"; m = s;
再定义两种操作,第一种操作:
- m = s;
- s = s + s;
第二种操作:
- s = s + m;
求最小的操作步骤数,可以将s拼接到长度等于n
思路
- 最后拼接结果的s一定是n个a,
- s一定是m的某个倍数,整数倍(对于操作一:s会是m的2倍;对于操作二:s一定是m的某个倍数,因此加上 m 也是 m 的整数倍)。
那么m是s的因子,本题转化为——m的最大值能是多少?实际上求的是m取得最大值最少需要几步,因为s一定是m的倍数
只有第一种操作的时候m会增加,且第一种操作m增加的速度比第二种快,因此我们实际上求的是第一次操作的最大次数是多少。
如果目标结果n为质数——则只能通过方法二一步一步的进行计算——步数为 n-1 次
不然目标结果n不是质数——则将n分解成质数的乘积
此时 m 的最大值可以达到 10 (2*10=20,当m到达10的时候,调用第一种操作使s变成20)。整个的拆分过程如下:
- 将n拆分成 a*b*c*d,abcd都是质数,因此对于F(a),需要a-1步才能实现,m的最大值是 b*c*d
- 对于b*c*d,又可以分解成b-1步凑出质数,以及m的极值 c*d
- 。。。
- 最终步骤为 a-1+b-1+c-1+d-1
例子
F(20)=2*2*5=F(2)+F(10).——目标是使得 m=2*5=10,F(10)代表s的值为10的情况。
如果希望m到达10,说明s曾经到达过10,这样执行步骤一,才会使得 m =10,s=20,所以最后一步是操作一。
因此F(20)=(2-1)+F(10)
F(60)=F(3*4*5)——目标是使得m =4*5=20,m如何到达20?
是s=20,然后调用步骤一,使得m=20,此时s=40.距离s=60还差一个m,因此最后一步调用的是操作二。
因此 F(60)=(3-1)+F(20)
那么,如何判断一个数是不是质数?
对于数字 n,尝试 将其与2到根号n之间的数字依次相除,如果能除开,说明不可以
实现代码
#include<iostream>
#include<math.h>
using namespace std;
int CreateNum(){
return rand();
}
//判断一个数是不是质数
bool judge(int n){
for(int i=2;i<=sqrt(n);i++){
if(!n%i) return true;
}
return false;
}
void minStep(int n){
cout<<"n的值为:"<<n<<endl;
if(n<2) return;
//判断n是不是质数
bool zhishu=judge(n);
if(zhishu){
cout<<n-1<<endl;
return;
}
//不是质数,此时需要获取质数的累加和以及质数的个数
//f(n)=f(a*b*c*d)=a-1+b-1+c-1+d-1
int sum=0, num=0;
for(int i=2; i<=n; i++){
//n的值会一直变小,当i=n时,说明这个n的各个质数因子被拆分完毕
while(n%i==0){
sum+=i;
num++;
n/=i;
}
}
cout<<"最少次数为:"<<sum-num<<endl;
}
int main(){
int time=10, n=0;
while(time--){
n=CreateNum();
minStep(n);
}
return 0;
}
题目四
给定一个字符串类型的数组arr,求其中出现次数最多的前K个
思路
先建立一个哈希表,统计每个字符串出现的次数,然利用小顶堆来进行统计。控制小顶堆的大小为K即可。
堆,在STL里面的优先队列,优先队列默认是大顶堆,因此使用时需要改一下参数。
所以可以用优先队列,也可以自己用数组去实现,因为堆排序底层就是数组实现的,数组中数字的关系是因为固定的数学公式来进行维持的(实现细节见:来啃硬骨头——Topk c++ (堆的概念,heapinsert、heapify的过程,在这里描述的很详细))
实现代码——优先队列版
/*
给定一个字符串类型的数组arr,求其中出现次数最多的前K个
*/
#include<iostream>
#include<unordered_map>
#include<vector>
#include<queue>
#include<string>
using namespace std;
struct Node{
string key;
int val;
Node(string i, int j):key(i),val(j){}
};
struct compare{
bool operator()(const Node& cur1, const Node& cur2){
return cur1.val>cur2.val;
}
};
void TopKTimes(vector<string>&con, int k){
if(con.empty()) return;
unordered_map<string, int>count;
priority_queue<Node, vector<Node>,compare> minHeap;
for(int i=0; i<con.size(); i++){
//没找到,则加入到哈希表中
if(count.find(con[i])==count.end())
count[con[i]]=1;
//找到了,则记录一下
else
count[con[i]]++;
}
//说明这些个字符串都是需要输出的
if(k>=count.size()){
for(auto i: count)
cout<<i.first<<endl;
return;
}
for(auto i: count){
Node cur(i.first, i.second);
if(minHeap.size()<k){
minHeap.push(cur);
}else{
if(cur.val>minHeap.top().val){
minHeap.pop();
minHeap.push(cur);
}
}
}
while(minHeap.size()>0){
cout<<minHeap.top().key<<endl;
minHeap.pop();
}
}
int main(){
vector<string>con={"abcdefg", "qwerty", "asdfgh", "zxcvbn", "abcdefg","abcdefg","qwerty"};
int k=3;
while(k){
cout<<"k="<<k<<"时的前k项为:"<<endl;
TopKTimes(con, k);
cout<<endl;
k--;
}
return 0;
}
实现代码——数组版
/*
给定一个字符串类型的数组arr,求其中出现次数最多的前K个
*/
#include<iostream>
#include<algorithm>
#include<vector>
#include<unordered_map>
using namespace std;
struct Node{
string key;
int val;
Node(string i, int j):key(i),val(j){}
};
struct compare{
bool operator()(Node i, Node j){
return i.val>j.val;
}
};
void TopKTimes(vector<string>&con, int k){
if((k<0)||!con.size()) return;
unordered_map<string, int>count;
for(auto i: con){
if(count.find(i)==count.end())
count[i]=1;
else
count[i]++;
}
vector<Node>res;
for(auto i : count){
Node cur(i.first, i.second);
res.push_back(cur);
}
compare com;
sort(res.begin(), res.end(), com);
for(int i=0; i<k; i++){
cout<<res[i].key<<endl;
}
}
int main(){
vector<string>con=
{"abcdefg", "qwerty", "asdfgh", "zxcvbn", "abcdefg","abcdefg","qwerty"};
int k=3;
while(k){
cout<<"k="<<k<<"时的前k项为:"<<endl;
TopKTimes(con, k);
k--;
}
return 0;
}
题目五——阿里原题
有n个打包机器从左到右一字排开,上方有一个自动装置会抓取一批放物品到每个打包机上,放到每个机器上的这些物品数量有多有少,由于物品数量不相同,需要工人将每个机器上的物品进行移动从而到达物品数量相等才能打包。每个物品重量太大、
每次只能搬一个物品进行移动,为了省力,只在相邻的机器上移动。请计算在搬动最小轮数的前提下,使每个机器上的物品数量相等。如果不能使每个机器上的物品相同,返回-1。例如[1,0,5]表示有3个机器,每个机器上分别有1、0、5个物品,经过这些轮后:
- 第一轮:1 0 <- 5 => 1 1 4
- 第二轮:1 <-1<- 4 => 2 1 3
- 第三轮:2 1 <- 3 => 2 2 2
移动了3轮,每个机器上的物品相等,所以返回3
例如[2,2,3]表示有3个机器,每个机器上分别有2、2、3个物品,
这些物品不管怎么移动,都不能使三个机器上物品数量相等,返回-1
思路:
先将所有的数累加sum,如果sum不能被n整除,则无论怎么分都不会成功。此时返回 -1.
对于位置 i :
- 如果他左侧需要10件,右侧需要20件,说明i位置富余30件,此时需要移动30次
- 如果他左侧需要10件,右侧富余20件,则需要移动20次
- 如果他左侧富余10件,右侧富余20件,则需要移动20次
- 如果他左侧富余20件,右侧需要10件,则需要移动20次。
因此,除了两侧都需要衣服的情况,其余情况的移动次数都是两侧的绝对值中的较大者。
两侧都需要衣服,则移动次数是两侧需要的衣服的累加和。
求出每一台机器需要移动的次数,最大的那个就是答案。
实现代码
#include<iostream>
#include<vector>
using namespace std;
int PackingMachine(vector<int>&num){
if(num.size()<=1) return 0;
int sum=0;
for(auto i : num)
sum+=i;
//不能整除,说明无论怎么分都无法满足
if(sum%num.size()!=0) return -1;
int avg=sum/num.size();
int leftneed=0, rightneed=0;
int lefthave=0;
int res=0;
for(int i=0; i<num.size(); i++){
leftneed=i*avg-lefthave;
rightneed=(num.size()-i-1)*avg-(sum-num[i]-lefthave);
lefthave+=num[i];
if( leftneed>0 && rightneed>0 ) res=max(res,rightneed+leftneed);
else res=max(res, max(abs(leftneed),abs(rightneed)));
}
return res;
}
int main(){
vector<int>num={4,2,8,6,2,7,0,7,4,10};
int ans=PackingMachine(num);
cout<<ans<<endl;
return 0;
}
题目六
用zigzag的方式打印矩阵,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
打印顺序为:0 1 4 8 5 2 3 6 9 10 7 11
思路
一律看宏观流程,找找宏观调度。不要把思路局限在怎么变上。
用四个变量表示两个点,如下图所示
A和B押中的点,都是处于一条斜线上。每次打印完一斜线之后,就将A向右移动,将B向下移动。对于从下向上打印还是从上向下打印,使用一个bool类型的变量进行控制即可。
这题本质就是斜线依次交替打印。
实现代码
#include<iostream>
#include<vector>
using namespace std;
void helper(vector<vector<int>>&num, int av, int ah, int bv, int bh, bool flag){
//打印顺序,先左上(flag=true时),后右下(flag=false时)
if(!flag){
//向左下方打印(坐标和二维矩阵的是不一样的,阿西吧)
//从(0,2)走到(2,0)
while(ah!=bh-1)
cout<<num[av++][ah--]<<endl;
}else{
//向左上方打印
//从(1,0)到(0,1)
while(bv!=av-1)
cout<<num[bv--][bh++]<<endl;
}
}
void Print(vector<vector<int>>&num){
if(num.size()==0) return;
int av=0, ah=0, bv=0, bh=0;
bool flag=true;
int hor=num[0].size()-1, ver=num.size()-1;
while(av!=(ver+1)){
helper(num, av, ah, bv, bh, flag);
//av为什么写在ah的上面?思考情况:
//ah=hor-1时,如果ah写在上面,则ah=ah+1,此时ah=hor,
//本来应该指向矩阵右上角的,结果av一看ah=hor了,就猥琐的执行了 av=av+1
av=ah==hor?av+1:av;
ah=ah==hor?ah:ah+1;
bh=bv==ver?bh+1:bh;
bv=bv==ver?bv:bv+1;
flag=!flag;
}
}
int main(){
vector<vector<int>>num={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
Print(num);
return 0;
}
题目八
用螺旋的方式打印矩阵,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
打印顺序为:0 1 2 3 7 11 10 9 8 4 5 6
思路
剥洋葱——对于一个框,给出左上角和右下角的点们就可以将这个框打印。打印完之后,左上角点向右下方移动,右下角点向左上方移动,去圈下一层的框框。
实现代码
#include<iostream>
#include<vector>
using namespace std;
void helper(vector<vector<int>>& matrix,
vector<int>&res, int ar, int ac, int br, int bc){
//说明此时要打印一条水平线
/*
1234
5678
1234
当打印中间的67的时候,会出现这种情况
*/
if(ar==br){
while(ac<bc+1){
res.push_back(matrix[ar][ac++]);
}
//此时打印一列的情况
/*
123
456
789
123
当打印中间的那一竖58的时候,会出现该种情况
*/
}else if(ac==bc){
while(ar<br+1){
res.push_back(matrix[ar++][ac]);
}
}else{
int hor=ac, ver=ar;
//打印上面的横向边框
while(hor<bc){
res.push_back(matrix[ar][hor++]);
}
//打印右侧的纵向边框
while(ver<br){
res.push_back(matrix[ver++][hor]);
}
//打印下方的横向边框
while(hor>ac){
res.push_back(matrix[ver][hor--]);
}
//打印左侧的竖向边框
while(ar<ver){
res.push_back(matrix[ver--][hor]);
}
}
}
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int>res;
if((matrix.size()==0)||(matrix[0].size()==0)) return res;
int ar=0, ac=0, br=matrix.size()-1, bc=matrix[0].size()-1;
while(1){
//打印边框的函数
helper(matrix, res,ar,ac,br,bc);
//打印之后移动指针,以便于打印内层的边框
ar++;
ac++;
br--;
bc--;
if((ar>br)||(ac>bc)) break;
}
for(auto i:res)
cout<<i<<" ";
return res;
}
int main(){
vector<vector<int>>matrix={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
spiralOrder(matrix);
cout<<endl;
return 0;
}
题目九
给定一个正方形矩阵,只用有限几个变量,实现矩阵中每个位置的数顺时针转动90度,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
矩阵应该被调整为:
12 8 4 0
13 9 5 1
14 10 6 2
15 11 7 3
思路
要想宏观逻辑,遇到矩阵变换就想宏观逻辑,别卡死在里面的细节上。
原地调整,用框来转动。对于下图,最外的那一层转好再考虑里层的转动。对于一个框,如何进行转动?
如果边长为4 ,那么可以分为类似下图中的三组形式。每组四个点,这四个点的位置相互交换即可。
实现代码
#include<iostream>
#include<vector>
using namespace std;
void helper(vector<vector<int>>&matrix, int a, int b, int c, int d){
int temp=0;
int time=d-c;
//将当组中四个元素相互交换位置
for(int i=0; i<time; i++){
temp=matrix[c-i][b];
matrix[c-i][b]=matrix[c][d-i];
matrix[c][d-i]=matrix[a+i][d];
matrix[a+i][d]=matrix[a][b+i];
matrix[a][b+i]=temp;
}
}
void rotate(vector<vector<int>>&matrix){
//(a,b)代表左上角的点,(a,d)代表右上角的点,(c,b)代表左下角的点
//注意,这里的第一个数代表行,第二个数代表列,别和坐标系中的表示方法搞混了
int a=0, b=0, c=matrix.size()-1, d=matrix[0].size()-1;
//一层一层往里拨
while(a<=c&&b<=d)
helper(matrix, a++, b++, c--, d--);
}
int main(){
vector<vector<int>>matrix={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
rotate(matrix);
return 0;
}