枚举和计数练习题及参考代码
01统计好三元组
问题描述
给你一个整数数组 arr
,以及 a
、b
、c
三个整数。请你统计其中好三元组的数量。
如果三元组 (arr[i], arr[j], arr[k])
满足下列全部条件,则认为它是一个 好三元组 。
0 <= i < j < k < arr.length
|arr[i] - arr[j]| <= a
|arr[j] - arr[k]| <= b
|arr[i] - arr[k]| <= c
其中 |x|
表示 x
的绝对值。
返回好三元组的数量 。
3 <= arr.length <= 100
0 <= arr[i] <= 1000
0 <= a, b, c <= 1000
输入描述
第一行输入一个整数n
第二行是n
个由空格分割的整数,代表数组arr
第三行是3个整数分别代表 a
、b
、c
输出描述
输出一个整数代表好三元组的数量
输入样例
6
3 0 1 1 9 7
7 2 3
输出样例
4
参考代码
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n,a,b,c;
cin>>n;
vector<int> arr(n);
for(int i=0;i<n;i++){
cin>>arr[i];
}
cin>>a>>b>>c;
int result = 0;
for(int i=0;i<n-2;i++){
for(int j=i+1;j<n-1;j++){
for(int k=j+1;k<n;k++){
if( abs(arr[i]-arr[j])<=a && abs(arr[j]-arr[k])<=b && abs(arr[i]-arr[k])<=c) result++;
}
}
}
cout<<result;
return 0;
}
02蛇形数组
问题描述
蛇形填充方法为:
对于每一条左下-右上的斜线,从左上到右下依次编号 按编号从小到大的顺序,将数字从小到大填入各条斜线,其中编号为奇数的从左下向右上填写,编号为偶数的从右上到左下填写。
现在,给你一个整数 n
,请问它在蛇形数组的第几行第几列?
- n ≤ 1 0 8 n \le 10^8 n≤108
输入描述
输入一个整数n
输出描述
输出n
所在的行列x y
输入样例
6
输出样例
1 3
参考代码
#include<iostream>
using namespace std;
int main(){
int n;
cin>>n;
int i=1,x=1,y=1,st=1,ch=0;
while(i<n){
i++;
if(x==1 && ch==0){
y++;
st=1;
ch=1;
continue;
}
if(y==1 && ch==0){
x++;
st=-1;
ch=1;
continue;
}
x += st;
y -= st;
ch=0;
}
cout<<x<<' '<<y<<endl;
return 0;
}
03手机信号
问题描述
已知你身边的信号塔位置 towers
和一个整数 radius
代表信号塔的信号覆盖范围。
towers
中信号塔表示为 towers[i] = [xi, yi, qi]
表示第 i
个网络信号塔的坐标是 (xi, yi)
且信号强度参数为 qi
。所有坐标都是在 X-Y 坐标系内的 整数 坐标。两个坐标之间的距离用 欧几里得距离 计算。
信号塔的信号覆盖范围 radius
表示一个塔能到达的 最远距离 。如果一个坐标跟塔的距离在 radius
以内(包含radius
),那么该塔的信号可以到达该坐标。在这个范围以外信号会很微弱,所以 radius
以外的距离该塔是 不能到达的 。
如果第 i
个塔能到达 (x, y)
,那么该塔在此处的信号为 ⌊qi / (1 + d)⌋
,其中 d
是塔跟此坐标的距离。一个坐标的 信号强度 是所有 能到达 该坐标的塔的信号强度之和。
请你返回数组 [cx, cy]
,表示 信号强度 最大的 整数 坐标点 (cx, cy)
。如果有多个坐标网络信号一样大,请你返回字典序最小的 非负 坐标。
注意:
-
坐标
(x1, y1)
字典序比另一个坐标(x2, y2)
小,需满足以下条件之一:- 要么
x1 < x2
, - 要么
x1 == x2
且y1 < y2
。
- 要么
-
⌊val⌋
表示小于等于val
的最大整数(向下取整函数)。 -
1 <= towers.length <= 50
-
towers[i].length == 3
-
0 <= xi, yi, qi <= 50
-
1 <= radius <= 50
输入描述
第一行一个整数 n
代表信号塔的个数,一个整数 r
代表信号塔的信号覆盖范围
其后 n
行,每行三个整数,分别代表 xi, yi, qi
输出描述
输出一个整数坐标cx cy
输入样例
3 2
1 2 5
2 1 7
3 1 9
输出样例
2 1
参考代码
#include<iostream>
#include<vector>
#include<math.h>
using namespace std;
int main(){
int n,r,minx=50,miny=50,maxx=0,maxy=0,maxr=0,resx,resy;
cin>>n>>r;
vector<vector<int>> towers(n,vector<int>(3,0));
for(int i=0;i<n;i++){
cin>>towers[i][0]>>towers[i][1]>>towers[i][2];
minx = min(minx, towers[i][0]);
maxx = max(maxx, towers[i][0]);
miny = min(miny, towers[i][1]);
maxy = max(maxy, towers[i][1]);
}
for(int i=minx;i<=maxx;i++){
for(int j=miny;j<=maxy;j++){
int ra = 0;
for(int k=0;k<n;k++){
int dis = sqrt((towers[k][0] - i)*(towers[k][0] - i) + (towers[k][1] - j)*(towers[k][1] - j));
if(dis<=r){
ra += towers[k][2] / (1+dis);
}
}
if(ra>maxr){
maxr = ra;
resx = i;
resy = j;
}
}
}
cout<<resx<<' '<<resy;
return 0;
}
04修剪灌木
问题描述
爱丽丝要完成一项修剪灌木的工作。
有 n
棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为0厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。
灌木每天从早上到傍晚会长高1厘米,而其余时间不会长高。在第一天的早晨,所有灌木的高度都是0厘米。爱丽丝想知道每棵灌木最高长到多高。
- 1 < n ≤ 10000 1<n\le10000 1<n≤10000
输入描述
一个正整数 n
代表灌木的个数
输出描述
输出 n
行,每行一个整数,第 i
行表示从左到右第 i
棵树最高能长到多高。
输入样例
3
输出样例
4
2
4
参考代码
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cout<<max(i*2-2,(n-i+1)*2-2)<<endl;
}
return 0;
}
05连续整数求和
问题描述
给定一个正整数 n
,返回 连续正整数满足所有数字之和为 n
的组数 。
例如:
输入: n = 5
输出: 2
解释: 5 = 2 + 3,共有两组连续整数([5],[2,3])求和后为 5。
- 1 < = n < = 1 0 9 1 <= n <= 10^9 1<=n<=109
输入描述
一个整数n
输出描述
输出一个整数
输入样例
5
输出样例
2
参考代码
#include<iostream>
#include<vector>
using namespace std;
bool isKConsecutive(int n, int k) {
if (k % 2 == 1) {
return n % k == 0;
} else {
return n % k != 0 && 2 * n % k == 0;
}
}
int main(){
int n;
cin>>n;
int result=0;
for(int k = 1; k * (k + 1) <= 2*n; k++) {
if (isKConsecutive(n, k)) {
result++;
}
}
cout<<result;
return 0;
}
如果正整数 n n n 可以表示成 k k k 个连续正整数之和,则由于 k k k 个连续正整数之和的最小值是 ∑ i = 1 k i = k ( k + 1 ) 2 \sum_{i = 1}^k i = \frac{k(k + 1)}{2} ∑i=1ki=2k(k+1) ,因此有 n ≥ k ( k + 1 ) 2 n \ge \frac{k(k + 1)}{2} n≥2k(k+1) ,即 k ( k + 1 ) ≤ 2 n k(k + 1) \le 2n k(k+1)≤2n。枚举每个符合 k ( k + 1 ) ≤ 2 n k(k + 1) \le 2n k(k+1)≤2n 的正整数 k k k,判断正整数 n n n 是否可以表示成 k k k 个连续正整数之和。
如果正整数 n n n 可以表示成 k k k 个连续正整数之和,假设这 k k k 个连续正整数中的最小正整数是 x x x,最大正整数是 y y y,则有 y = x + k − 1 y = x + k - 1 y=x+k−1,根据等差数列求和公式有 n = k ( x + y ) 2 = k ( 2 x + k − 1 ) 2 n = \frac{k(x + y)}{2} = \frac{k(2x + k - 1)}{2} n=2k(x+y)=2k(2x+k−1) , x = n k − k − 1 2 x = \frac{n}{k} - \frac{k - 1}{2} x=kn−2k−1 ,根据 k ( k + 1 ) ≤ 2 n k(k + 1) \le 2n k(k+1)≤2n 可知 x > 0 x > 0 x>0。分别考虑 k k k 是奇数和偶数的情况。
-
当 k k k 是奇数时, k − 1 k - 1 k−1 是偶数,因此 2 x + k − 1 2x + k - 1 2x+k−1 是正偶数。令 q = 2 x + k − 1 2 q = \frac{2x + k - 1}{2} q=22x+k−1 ,则 qq 是正整数, n = k q n = kq n=kq, q = n k q = \frac{n}{k} q=kn 。由于 q q q 是正整数,因此 n n n 可以被 k k k 整除。
当 n n n 可以被 k k k 整除时,由于 $\frac{n}{k} $ 和 k − 1 2 \frac{k - 1}{2} 2k−1 都是整数,因此 x = n k − k − 1 2 x = \frac{n}{k} - \frac{k - 1}{2} x=kn−2k−1 是整数。又由于 x > 0 x > 0 x>0,因此 x x x 是正整数。因此 n n n 可以表示成 k k k 个连续正整数之和。
综上所述,当 k k k 是奇数时,「正整数 n n n 可以表示成 k k k 个连续正整数之和」等价于「正整数 n n n 可以被 k k k 整除」。 -
当 k k k 是偶数时,2x + k - 12x+k−1 是奇数。将 n = k ( 2 x + k − 1 ) 2 n = \frac{k(2x + k - 1)}{2} n=2k(2x+k−1) 写成 $\frac{2x + k - 1}{2} = \frac{n}{k} $ ,由于 2x + k - 12x+k−1 是奇数,因此 $\frac{2x + k - 1}{2} $ 不是整数, n n n 不可以被 k k k 整除,又由于 2 x + k − 1 = 2 n k 2x + k - 1 = \frac{2n}{k} 2x+k−1=k2n是整数,因此 2n2n 可以被 k k k 整除。
当 n n n 不可以被 k k k 整除且 2n2n 可以被 k k k 整除时,$\frac{2n}{k} $ 一定是奇数(否则 $\frac{n}{k} $是整数,和 n n n 不可以被 k k k 整除矛盾),令 $\frac{2n}{k} = 2t + 1 $,其中 t t t 是整数,则 $\frac{n}{k} = t + \frac{1}{2} $。此时 x = n k − k − 1 2 = t + 1 2 − k 2 + 1 2 = t − k 2 + 1 x = \frac{n}{k} - \frac{k - 1}{2} = t + \frac{1}{2} - \frac{k}{2} + \frac{1}{2} = t - \frac{k}{2} + 1 x=kn−2k−1=t+21−2k+21=t−2k+1,由于$ \frac{k}{2} $ 是整数,因此 x x x 是整数。又由于 x > 0 x > 0 x>0,因此 x x x 是正整数。因此 n n n 可以表示成 k k k 个连续正整数之和。
综上所述,当 k k k 是偶数时,「正整数 n n n 可以表示成 k k k 个连续正整数之和」等价于「正整数 n n n 不可以被 k k k 整除且正整数 2 n 2n 2n 可以被 k k k 整除」。
根据上述分析,可以得到判断正整数 n n n 是否可以表示成 k k k 个连续正整数之和的方法:
-
如果 $k $是奇数,则当 n n n 可以被 k k k 整除时,正整数 n n n 可以表示成 k k k 个连续正整数之和;
-
如果 k k k 是偶数,则当 n n n 不可以被 k k k 整除且 2 n 2n 2n 可以被 k k k 整除时,正整数 n n n 可以表示成 k k k 个连续正整数之和。
06号码牌
问题描述
给你从0到1的号码牌各 n
张,你需要用这些号码牌从1开始拼出正整数,每拼一个就保存起来,号码牌就不能用来拼其他的数了,请问你可以拼到多少?
例如:
当你有30张号码牌,其中0到9各3张,则你可以拼出1到10,但是拼11时号码牌已经只有一张了,不够拼出11.
- 1 < = n < = 1 0 7 1 <= n <= 10^7 1<=n<=107
输入描述
一个整数n
输出描述
输出一个整数
输入样例
3
输出样例
10
参考代码
#include <iostream>
#include <vector>
using namespace std;
vector<int> num;
bool check(int i)
{
while(i)
{
if(--num[i%10]<0){return false;}
i /= 10;
}
return true;
}
int main()
{
int n;
cin>>n;
num = vector<int>(10,n);
int i=1;
while(check(i)) i++;
cout<<--i;
return 0;
}
07第一个唯一字符
问题描述
给你一个字符串 s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1
。
- 1 ≤ s . l e n g t h ≤ 1 0 5 1 \le s.length \le 10^5 1≤s.length≤105
s
只包含小写字母
输入描述
一个字符串 s
输出描述
输出一个整数
输入样例
aabb
输出样例
-1
参考代码
#include<iostream>
#include<unordered_map>
#include<string>
using namespace std;
int main()
{
string s;
unordered_map<char,int> map;
cin>>s;
for(char c:s){
map[c]++;
}
for(int i=0;i<s.length();i++){
if(map[s[i]]==1){
cout<<i;
return 0;
}
}
cout<<-1;
return 0;
}
08数青蛙
问题描述
给你一个字符串 s
,它表示不同青蛙发出的蛙鸣声(字符串 croak
)的组合。由于同一时间可以有多只青蛙呱呱作响,所以s
中会混合多个 croak
。
请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。
要想发出蛙鸣 croak
,青蛙必须 依序 输出 ‘c’, ’r’, ’o’, ’a’, ’k’
这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。如果字符串 s
不是由若干有效的 croak
字符混合而成,请返回 -1 。
示例 1:
输入:croakcroak
输出:1
解释:一只青蛙 “呱呱” 两次
示例 2:
输入:crcoakroak
输出:2
解释:最少需要两只青蛙,“呱呱” 声用黑体标注
第一只青蛙 “crcoakroak”
第二只青蛙 “crcoakroak”
示例 3:
输入:croakcrook
输出:-1
解释:给出的字符串不是 “croak” 的有效组合。
1 <= croakOfFrogs.length <= 105
- 字符串中的字符只有
'c'
,'r'
,'o'
,'a'
或者'k'
输入描述
一个字符串 s
输出描述
输出所有蛙鸣所需不同青蛙的最少数目
输入样例
croakcroak
输出样例
1
参考代码
#include<iostream>
#include<unordered_map>
#include<string>
using namespace std;
int main()
{
string s;
cin>>s;
int c=0;
int r=0;
int o=0;
int a=0;
int k=0;
int re=0;
bool flag=true;
for(int i=0; i<s.size(); i++){
if (s[i]=='c') c++;
if (s[i]=='r') r++;
if (s[i]=='o') o++;
if (s[i]=='a') a++;
re=max(re, c);//遇到k前要判断有多少个c同时存在
if (s[i]=='k'){//遇到k就要规约一个croak
k++;
if (c>=r && r>=o && o>=a && a>=k){
c--;
r--;
o--;
a--;
k--;
}
}
if(!(c>=r && r>=o && o>=a && a>=k)){//必须保持任意时刻(c>=r>=o>=a>=k),才是正确的;否则就是错误的,
flag=false;
break;
}
}
if (c!=0 || r!=0 || o!=0 || a!=0 ||k!=0) flag=false;//如果最后有剩的字母,也是错误的
if (flag==true) cout<< re;
else cout<<-1;
return 0;
}
09数组对是否可以被 k 整除
问题描述
给你一个整数数组 arr
和一个整数 k
,其中数组长度是偶数,值为 n
。
现在需要把数组恰好分成 n / 2
对,以使每对数字的和都能够被 k
整除。
如果存在这样的分法,请返回 true
;否则,返回 false
。
arr.length == n
- ` 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1≤n≤105
n
为偶数- − 1 0 9 ≤ a r r [ i ] ≤ 1 0 9 -10^9 \le arr[i] \le 10^9 −109≤arr[i]≤109
- 1 ≤ k ≤ 1 0 5 1 \le k \le 10^5 1≤k≤105
输入描述
第一行一个整数 n
表示数组的长度,一个整数k
第二行n
个由空格分割的整数,表示数组的内容。
输出描述
如果存在这样的分法,请返回 true
;否则,返回 false
。
输入样例
6 7
1 2 3 4 5 6
输出样例
true
参考代码
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n,k;
vector<int> arr;
vector<int> mod;
cin>>n>>k;
arr = vector<int>(n,0);
mod = vector<int>(k,0);
for(int i=0;i<n;i++){
cin>>arr[i];
}
// 统计余数为0到k-1的个数
for (int num: arr) {
++mod[(num % k + k) % k];
}
// 余数为i与余数为k-i个数不相等则不能配对
for (int i = 1; i + i < k; ++i) {
if (mod[i] != mod[k - i]) {
cout<<"false";
return 0;
}
}
// 余数为0的个数必须为偶数
if( mod[0] % 2 != 0 ){
cout<<"false";
return 0;
}
cout<<"true";
return 0;
}
10序列中不同最大公约数的数目
问题描述
给你一个由正整数组成的数组 nums
。
数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。
例如,序列 [4,6,16]
的最大公约数是 2
。
数组的一个 子序列 本质是一个序列,可以通过删除数组中的某些元素(或者不删除)得到。
例如,[2,5,10]
是 [1,2,1,2,4,1,5,10]
的一个子序列。
计算并返回 nums
的所有 非空 子序列中 不同 最大公约数的 数目 。
示例 1:
输入:nums = [6,10,3]
输出:5
解释:上图显示了所有的非空子序列与各自的最大公约数。
不同的最大公约数为 6 、10 、3 、2 和 1 。
示例 2:
输入:nums = [5,15,40,5,6]
输出:7
- 1 ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le nums.length \le 10^5 1≤nums.length≤105
- 1 ≤ n u m s [ i ] ≤ 2 ∗ 1 0 5 1 \le nums[i] \le 2 * 10^5 1≤nums[i]≤2∗105
输入描述
第一行一个整数 n
表示数组的长度
第二行n
个由空格分割的整数,表示数组的内容。
输出描述
如果存在这样的分法,请返回 true
;否则,返回 false
。
输入样例
3
6 10 3
输出样例
5
参考代码
#include<iostream>
#include<vector>
using namespace std;
int gcd(int a, int b){
if(b){
return gcd(b, a%b);
}
return a;
}
int main(){
int n,c=0;
cin>>n;
vector<int> nums(n,0);
for(int i=0;i<n;i++){
cin>>nums[i];
c = max(c, nums[i]);
}
vector<int> g(c + 1);
for (int x: nums) {
for (int y = 1; y * y <= x; ++y) {
if (x % y == 0) {
if (!g[y]) {
g[y] = x;
}
else {
g[y] = gcd(g[y], x);
}
if (y * y != x) {
int z = x / y;
if (!g[z]) {
g[z] = x;
}
else {
g[z] = gcd(g[z], x);
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= c; ++i) {
if (g[i] == i) {
++ans;
}
}
cout<<ans;
return 0;
}
假设最大的数是maxnum
,总体思路就是遍历[1,maxnum]
,检查每一个值x是否可能是一个序列的最大公约数。
如何检查呢?显然,这个序列中的每一个数都是x的倍数,这是一个形如[1x,2x...nx] (nx<=maxnum)
的序列。但这只是必要条件,并不是充分条件。比如x=2,序列[4,6,8]
的最大公约数是2,而序列[4,8]
的最大公约数是4。我们发现,这个序列不仅是x的倍数,还可以是x的某个倍数的倍数。于此同时,我们知道最大公约数的一个重要性质gcd(a,b,c)=gcd(gcd(a,b),c)
,这告诉我们,序列中加入的数越多,最大公约数只会越小。当我们把数组中所有x的倍数都加入了序列,如果求得的最大公约数是x,那就说明x是一个解了。如果此时求得的最大公约数仍然是x的某个倍数,就说明x不能构成任何一个序列的最大公约数。
分解每一个数的约数,这样能够直接得到x的所有倍数集合,然后求每个集合的最大公约数。
在这里,divisor[4]=[1,2,4]
, divisor[6]=[1,2,3,6]
,divisor[8]=[1,2,4,8]
, 所以:
g[1]=[4,6,8], gcd(4,6,8) = 2 != 1
g[2]=[4,6,8], gcd(4,6,8) = 2 == 2
g[3]=[6], gcd(6) = 6 != 3
g[4]=[4,8], gcd(4,8) = 4 == 4
g[5]=[]
g[6]=[6], gcd(6) = 6 == 6
g[7]=[]
g[8]=[8], gcd(8) = 8 == 8