今年是什麼情況???這筆試題都是競賽題難度呀,菜雞表示很受傷。
多多少少做了一些大廠的筆試題,大佬勿噴,我覺得很多都是勸退筆試。
拼多多筆試題
求方差
給定n個整數,請找出其中3個數,滿足這3個數的組合是所有組合中方差最小的。
輸入描述:
共兩行,第一行一個整數n(2<n<3000)
第二行是n個整數
題目中出現的所有數字的絕對值小於100000
思路:先排個序,之後組成方差的3個數必然是連在一起的,逐個求,記錄最小值就好
#include<bits/stdc++.h>
using namespace std;
int a[3001]
int main() {
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
sort(a, a + n);
double val, temp;
//我之前因爲res初始化的比較小,導致了部分樣例是沒過得
double res = 10000000;
for(int i = 0; i < n - 2; i++) {
val = (a[i] + a[i+1] + a[i+2]) / 3.0;
temp = ((a[i] - val) * (a[i] - val) + (a[i+1] - val) * (a[i+1] - val) + (a[i+2] - val) * (a[i+2] - val)) / 3.0;
res = min(res, temp);
}
printf("%.2lf", res);
return 0;
}
移動珍珠最小距離
多多雞有一串長度爲L的珍珠項鍊,上面有N顆珍珠,分佈在0到L-1這些位置上。現在多多雞想把所有的珍珠都移動到一起,並且想讓所有的珍珠移動的距離總和儘可能小。
所有的珍珠可以看作在一個環上,珍珠可以向相鄰的沒有珍珠的位置移動。
請輸出最優方案下的所有珍珠移動總和。
輸入描述:
共兩行,第一行兩個整數L、N(2 < N < L 100000)
第二行N個整數,表示每個珍珠所在的位置
題目保證所有珍珠的位置各不相同
分析:
滑動窗口問題,可以先考慮,如果問題不是成環的,而是線性的,該怎麼用滑動窗口解決
遞增序列總數
給定兩個正整數N和S,你需要找出所有的長度爲N的正整數數列,滿足單調遞增以及總和爲S的數列有多少個。
輸入描述:
共一行,兩個整數,N和S( 1 < N ,S < 100000)
輸出描述:
共一行,爲滿足條件的數列個數對1e9+7取模的結果
分析:
…也不會做
我們知道數列 1 到 n 求和爲 n * (n + 1) / 2
所以如果出現 n * (n + 1) / 2 > S那麼就是無解
結束後羣裏有大佬給了個解答:
用記憶化搜索求:
假設最小的數爲1, 那麼給N個數都加上初始值1,子問題成爲求長度爲N - 1,和爲S - N的遞增序列的個數。否則,依舊給N個數加上初始值,子問題成爲求長度爲N,和爲S-N 的遞增序列個數
f ( N , S) = f( N - 1, S - N) + f(N , S - N)
//
// test.cpp
// LeetCode
//
// Created by huangyi on 2019/7/4.
// Copyright © 2019 Leetcode. All rights reserved.
//
#include<bits/stdc++.h>
using namespace std;
int n, s;
const int mod = 1e9 + 7;
int cache[501][100001];
int count(int n, int s) {
if(s <= 0) {
return 0;
}
if(n * (n + 1) / 2 > s) {
return 0;
}
if(n == 1) {
return 1;
}
if(cache[n][s] != -1) {
return cache[n][s];
}
cache[n][s] = count(n, s - n) + count(n - 1, s - n);
if(cache[n][s] >= mod) {
cache[n][s] -= mod;
}
return cache[n][s];
}
int main()
{
scanf("%d %d", &n, &s);
memset(cache, -1, sizeof(cache));
int res = 0;
res = count(n, s);
printf("%d\n", res);
return 0;
}
多多雞爬山
多多雞和同事們跑去大山裏露營,總共有N座山,所有的山按編號從小到大分佈在一條直線上。每座山的山頂上都有多多雞的同事。露營需要持續一個月,多多雞負責露營物資的運送和垃圾的回收,它需要每天開車去每座山的山頂一趟。這邊的路可以分爲三種,第一種從山底去到山頂的路,第二種從山頂到山底的路,第三種是山頂間的路。每座山都有對應的第一種和第二種路,某些山頂之間有第三種路相連。山頂間的路只有某些山頂之間纔有,並且只能從編號小的山開向編號大的山。
多多雞每天都需要從山底出發,去到所有的山頂。運送物資給同事們。因爲山路的特殊性,他可能需要來回山底山頂多次。現在多多雞想找出一個方案,可以保證每個山頂都去過至少一次的情況下,上山的次數儘可能少。
分析:
… 直接放棄,不會
結束後,某大佬說直接套 最小路徑覆蓋的板子就可以…
我當時…最小路徑覆蓋是什麼鬼…
字節跳動
起牀
當時第一題只過了0.8,最後交卷了也不知道咋回事,哪個地方沒過,結束後有人幫我指出來了
題目裏面提到了保證至少有一個鬧鐘可以讓牛牛及時到達教室,所以設置初始值的時候就要很當心,別漏了第一個鬧鐘、
#include<bits/stdc++.h>
using namespace std;
int main() {
int n,hi[101] = {0}, mi[101] = {0}, x, a, b, t[101] = {0};
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d %d", &hi[i], &mi[i]);
t[i] = h[i] * 60 + mi[i];
}
scanf("%d", &x);
scanf("%d %d", &a, &b);
int sum = a * 60 + b;
int temp = sum - x;
//我當時應該是這裏錯了
int max = t[0];
for(int i = 0; i < n; i++) {
if(t[i] <= temp && t[i] >= max) {
max = t[i];
}
}
int h1 = max / 60;
int m1 = max % 60;
printf("%d %d\n", h1, m1);
return 0;
}
解密
小明和安琪是好朋友,最近,他們的談話被一家偵察機構監控,所以他們想將他們的談話內容進行加密處理。於是,他們發明了一種新的加密方式,每條信息都被編譯成二進制數B(明文),其長度爲N。然後該信息被寫下k次,每次向右移動0,1,。。。K-1位。
然後對每一列進行異或操作,並且把最終所得結果記錄下來,記爲S密文。
輸入描述:
第一行兩個數N和K
第二行輸入一個二進制字符串S,長度爲N+K-1
1 <= N <= 1e6
1 <= K <= 1e6
輸出描述:
輸出明文B
示例:
輸入:
6 2
1110001
輸出:
101111
#include<bits/stdc++.h>
using namespace std;
int n, k, char s[2000010], ans[1000010];
int main() {
scanf("%d %d", &n, &k);
scanf("%s", s);
ans[0] = s[0];
int now = s[0] - '0';
for(int i = 1; i < n; i++) {
ans[i] = now ^ (s[i] - '0') + '0';
now = now ^ (ans[i] - '0');
if(i >= k - 1) {
now = now ^ (ans[i-k+1] - '0');
}
ans[n] = '\0';
printf("%s\n", ans);
return 0;
}
}
發獎金
題目要求參考LeetCode135發糖果,基本一致
#include<bits/stdc++.h>
using namespace std;
int main() {
int n,v;
vector<int> nums;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &v);
nums.push_back(v);
}
vector<int> price(n, 100);
for(int i = 1; i < n; i++) {
if(nums[i] > nums[i-1]) {
price[i] = price[i-1] + 100;
}
}
for(int i = n - 2; i >= 0; i--) {
if(nums[i] > nums[i+1]) {
price[i] = max(price[i], price[i+1] + 100);
}
}
int res = 0;
for(int i = 0; i < n; i++) {
res += price[i];
}
printf("%d\n", res);
return 0;
}
跑步
…不會,看不懂題目,不過大佬們說了是個樹形dp。
牛客上看到有大佬發代碼了,貼出來學習學習,我是看不懂了
作者:Nagichan
鏈接:https://www.nowcoder.com/discuss/221211
來源:牛客網
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
const int MOD = 1e9 + 7;
int n;
LL ans[3], cnt[N][3], sum[N][3];
vector<int> G[N];
void dfs(int u, int fa) {
//cout << u << ' ' << fa << endl;
for(int i = 0; i < 3; i++) {
cnt[u][i] = sum[u][i] = 0;
}
for(auto v : G[u]) {
if(v == fa)
continue;
dfs(v, u);
for(int x = 0; x < 3; x++) {
for(int y = 0; y < 3; y++) {
(ans[(x + y + 1) % 3] +=
cnt[v][y] * sum[u][x] % MOD
+ cnt[u][x] * sum[v][y] % MOD
+ cnt[u][x] * cnt[v][y] % MOD) %= MOD;
}
}
for(int x = 0; x < 3; x++) {
(cnt[u][(x + 1) % 3] += cnt[v][x]) %= MOD;
(sum[u][(x + 1) % 3] += (sum[v][x] + cnt[v][x]) % MOD) %= MOD;
}
}
cnt[u][0]++;
for(int x = 0; x < 3; x++) {
ans[x] += sum[u][x];
}
}
int main() {
scanf("%d", &n);
for(int i = 0, u, v; i < n - 1; i++) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, -1);
cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << endl;
return 0;
}
美團筆試題
美團筆試題題型爲:40選擇+1問答+2編程
選擇題完全隨機,啥題型都有,點進去就看到微積分和線性代數等
問答題:
Restful調用和RPC調用什麼區別?如何設計一個RPC服務治理系統?Service mesh是爲了解決什麼問題?
編程題實際上考了兩道LeetCode原題:269和763,只不過換了下背景
快遞員分界
字符串 S 由大寫字母組成。我們要把這個字符串劃分爲儘可能多的片段,同一個字母只會出現在其中的一個片段。返回一個表示每個字符串片段的長度的列表。
輸入: S = "ABABCBACADEFEGDEHIJHKLIJ"
輸出: [9,7,8]
解釋:
劃分結果爲 "ABABCBACA", "DEFEGDE", "HIJHKLIJ"。
每個字母最多出現在一個片段中。
思路:
開一個數組記錄某個字母最後一次出現的下標i
從首字符開始遍歷字符串,找到首字符最後出現的位置,比較在該區間內字符在字符串中最後出現的位置是否超出區間,如果超出則將區間更新
#include<bits/stdc++.h>
using namespace std;
int main() {
char a[1001];
scanf("%s", a);
int last[30];
for(int i = 0; i < 26; i++) {
last[i] = 0;
}
vector<int> v;
for(int i = 0; i < strlen(a); i++) {
last[a[i] - 'A'] = i;
}
for(int i = 0; i < strlen(a); i++) {
int left = i, index = i, right = last[a[i] - 'A'];
while(index <= right) {
right = max(right, last[a[index++] - 'A']);
}
v.push_back(right - left + 1);
i = right;
}
for(int i = 0; i < v.size(); i++) {
if(i != v.size() - 1) {
printf("%d ", v[i]);
} else {
printf("%d\n", v[i]);
}
}
return 0;
}
火星文字典
題目描述:
已知一種新的火星文的單詞由英文字母組成,但是此火星文中的字母先後順序未知。給出一組非空的火星文單詞,且此組單詞已經按火星文字典序進行好了排序,請推斷出此火星文的字母先後順序。
輸入:
一行文本,爲一組按火星文字典序排序好的單詞(單詞兩端無引號),單詞之間通過空格隔開
輸出:
按火星文字母順序輸出出現過的字母,字母之間無其他字符,如果無法確定順序或者無合理的字母排序可能,請輸出“invalid”。
樣例輸入:
wrt wrf er ett rftt
樣例輸出:
wertf
思路:
按順序小的連接邊建圖,然後拓撲排序
#include<bits/stdc++.h>
using namespace std;
char s[100010], ss[1010][1010];
int G[30][30], indeg[30], have[30];
int main() {
gets(s);
for(int i = 0; i < 30; i++) {
have[i] = 1;
indeg[i] = 10;
}
int cnt = 0, len = 0, up = 0, time = 0;
for(int i = 0; i <= strlen(s); i++) {
if(s[i] == ' ' || i == strlen(s)) {
ss[cnt++][len] = '\0';
up = max(up, len);
len = 0;
} else {
ss[cnt][len++] = s[i];
time += have[s[i] - 'a'];
have[s[i] - 'a'] = 0;
indeg[s[i] - 'a'] = 0;
}
}
for(int i = 0; i < 30; i++) {
for(int j = 0; j < 30; j++) {
G[i][j] = 0;
}
}
for(int j = 0; j < up; j++) {
int last = 0;
for(int i = 1; i < cnt; i++) {
if(j >= strlen(ss[i])) {
continue;
}
bool flag = true;
for(int k = 0; k < j; k++)
if( ss[last][k] != ss[i][k])
flag = false;
if(flag && ss[last][j] != ss[i][j]) {
G[ss[last][j] - 'a'][ss[i][j] - 'a'] = 1;
indeg[ss[i][j] - 'a']++;
}
last = i;
}
}
string ans = "";
queue<int> q;
for(int i = 0; i < 30; i++) {
if(indeg[i] == 0) {
q.push(i);
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
ans += (char)u + 'a';
for(int i = 0; i < 26; i++) {
if(G[u][i] == 1) {
indeg[i]--;
if(indeg[i] == 0) {
q.push(i);
}
}
}
}
if(ans.length() == time) {
cout<<ans<<endl;
} else {
cout<<"invalid"<<endl;
}
return 0;
}
京東筆試題
京東題型是選擇+編程,也挺難的(個人覺得)
編程題有兩道
合唱隊
題目描述:
合唱隊的N名學生站成一排且從左到右編號爲1到N,其中編號爲i的學生身高爲H。現在將這些學生分成若干組(同一組的學生編號連續),並讓每組學生從左到右按身高從低到高進行排列,使得最後所有學生同樣滿足從左到右身高從低到高(中間位置可以等高),那麼最多可以將這些學生分成多少組?
輸入:
第一行包含一個整數N , 1 <= N <= 1e5
第二行包含N的空格隔開的整數Hi 到 HN ,1 <= Hi <= 1e9
輸出:
輸出能分成的最多組數
樣例輸入:
4
2 1 3 2
樣例輸出:
2
分析:
保證區間前半段的最大值小於等於後半段的最小值
可以開兩個輔助數組
leftMax[index]用於記錄[1, index]的最大值
rightMin[index]用於記錄[index, arrSize]的最小值
可以參考下LeetCode768
#include<bits/stdc++.h>
using namespace std;
int a[100001], leftMax[100001], rightMin[100001];
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int res = 1;
leftMax[1] = a[1];
for(int i = 2; i <= n; i++) {
leftMax[i] = max(leftMax[i-1], a[i]);
}
rightMin[n] = a[n];
for(int i = n - 1; i >= 1; i--) {
rightMin[i] = min(rightMin[i+1], a[i]);
}
for(int i = 1; i < n; i++) {
if(leftMax[i] <= rightMin[i+1]) {
res++;
}
}
printf("%d\n",res);
return 0;
}
考場安排
輸入:
第一行兩個整數n和m,表示有n個男生和n個女生,有m個朋友關係
1 <= n <= 500 , 1 <= m <= 100000
接下來m行,每行有兩個整數,x和y,表示第x號男生和第y號女生是朋友,男生編號爲[1,n],女生編號爲[n+1, 2n]
輸出:
輸出第一行包含一個整數a,表示最少需要搬出教室的人數
輸出第二行有a個整數,即a個需要搬出教室的人的編號,要求人數最少,且字典序最小
樣例輸入:
2 2
1 3
1 4
樣例輸出:
1
1
分析:二分圖最小頂點覆蓋問題,匈牙利算法
…考場上交了個匈牙利寫的,只過了0.36???
滴滴筆試題
滴滴的筆試題個人覺得也是勸退題,(大佬勿噴)。
題型爲20選擇+2編程(編程題很難,ACM銀牌以上可以嘗試下)
算式轉移
分析:
這道題我的理解就是排序,碰到連續加、連續減、連續乘或者連續除的序列就進行排序,注意只能操作相鄰的兩個數。
移除序列
上述兩題的圖片來源網友。
另外提一下,移除序列這道題的出處應該是codefroces Sereja and Two Sequences
CF參考代碼
#include<bits/stdc++.h>
using namespace std;
int n, total, cost;
#define INF 0x3f3f3f
int a[50001];
int b[50001];
vector<int> v[50001];
int dp[50001][310];
int main() {
memset(dp, INF, sizeof(dp));
scanf("%d %d %d", &n, &total, &cost);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
v[b[i]].push_back(i);
}
int tot = total / cost;
int res = 0;
dp[0][0] = 0;
for(int i = 1; i <= n; i++) {
dp[i][0] = 0;
for(int j = 1; j <= tot; j++) {
dp[i][j] = dp[i-1][j];
int temp = upper_bound(v[a[i]].begin(), v[a[i]].end(), dp[i][j-1]) - v[a[i]].begin();
if(temp < v[a[i]].size()) {
dp[i][j] = min(dp[i][j], v[a[i]][temp]);
}
if(j > res && j * cost + i + dp[i][j] <= total) {
res = j;
}
}
}
printf("%d\n", res);
return 0;
}
這道題好難理解呀。
看了題解後:考察的應該是dp+二分。需要優化
這道題看完我就想交卷了…打擾了,告辭
攜程筆試題
題型爲:20選擇+3編程
分隔鏈表
類似LeetCode86:可以參考:
https://blog.csdn.net/hy971216/article/details/82830998
大致意思就是給你一個單鏈表和一個整數m,要求你把鏈表裏小於等於m的結點放到前面,大於m的結點放到後面
參考代碼:
#include<bits/stdc++.h>
using namespace std;
//定義鏈表結點結構
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
//分隔鏈表
ListNode* partition(ListNode* head, int x) {
//定義兩個啞結點和兩個指針
ListNode dummy1(0),dummy2(0);
ListNode* p1 = &dummy1;
ListNode* p2 = &dummy2;
//定義輔助指針指向表頭
ListNode* p = head;
while(p) {
//如果小於等於x,就接到p1後面
if(p->val <= x) {
p1->next = p;
p1 = p1->next;
} else {
//反之接到p2後面
p2->next = p;
p2 = p2->next;
}
p = p->next;
}
p2->next = NULL;
//讓p1指向p2
p1->next = dummy2.next;
return dummy1.next;
}
//構建單鏈表
void createList(ListNode* head, int n) {
cin >> head->val;
n--;
for(int i = 0; i < n; i++) {
//尾插法插入結點
ListNode* p = new ListNode(0);
cin >> p->val;
p->next = nullptr;
head->next = p;
head = p;
}
}
int main() {
ListNode* head = new ListNode(0);
head->next = nullptr;
createList(head, 6);
ListNode* res = partition(head, 3);
while(res) {
cout << res->val;
cout << " ";
res = res->next;
}
return 0;
}
反轉子串
參考代碼:
#include<bits/stdc++.h>
using namespace std;
int num[100000];
stack<int> a;
string resolve(string s) {
string res = "";
int len = s.size();
if(len <= 2) {
return res;
}
for(int i = 0; i < len; i++) {
if(s[i] == '(') {
a.push(i);
} else if(s[i] == ')') {
//這個判斷不能漏..不然只能過%71
if(a.empty()) {
return "";
}
num[i] = a.top();
num[a.top()] = i;
a.pop();
}
}
int flag = 1;
int i = 0;
while(i < len) {
if(s[i] == '(' || s[i] == ')') {
i = num[i];
flag = -flag;
} else {
res += s[i];
}
i += flag;
}
return res;
}
int main() {
string s;
cin>>s;
cout<< resolve(s) <<endl;
return 0;
}
調度隊列
參考LeetCode 410分隔數組的最大值;
題目保證了沒有負數,如果有負數還要修改下思路
給定一個非負整數數組和一個整數 m,你需要將這個數組分成 m 個非空的連續子數組。設計一個算法使得這 m 個子數組各自和的最大值最小。
這題還是考察二分和貪心的思想,有人用dp做,但是dp的話數據量太大就過不了了
參考代碼:
#include<bits/stdc++.h>
using namespace std;
int n, m;
typedef long long ll;
int splitArray(vector<int>& nums, int m) {
ll left = 0, right = 0;
int n = nums.size();
for(int i = 0; i < n; i++) {
right += nums[i];
if(left < nums[i]) {
left = nums[i];
}
}
ll res = right;
while(left <= right) {
ll mid = (left + right) >> 1;
ll sum = 0;
int cnt = 1;
for(int i = 0; i < n; i++) {
if(sum + nums[i] > mid) {
cnt++;
sum = nums[i];
} else {
sum += nums[i];
}
}
if(cnt <= m) {
res = min(res, mid);
right = mid - 1;
} else {
left = mid + 1;
}
}
return res;
}
int main() {
scanf("%d %d", &m, &n);
vector<int> nums;
for(int i = 0; i < n; i++) {
scanf("%d", &x);
nums.push_back(x);
}
int res = splitArray(nums, m);
printf("%d\n",res);
return 0;
}
網易互娛
網易互娛我這批的筆試題其實不難,只是我比較菜而已。
第二題因爲平時練得太少,都不知道怎麼根據結點編號構建二叉樹了
第三題運氣好過了測試用例,實際提交只能過0…
迴文數
第一題就是判斷給定整數的二進制形式是不是一個迴文數(忽略前導0)
簡單的想法就是開一個數組保存這個數的二進制形式,然後判斷這個數組是否迴文即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int t;
ll x;
scanf("%d", &t);
while(t--) {
bool flag = false;
int a[111];
int index = 0;
scanf("%lld", &x);
while(x != 0) {
a[index++] = x % 2;
x = x / 2;
}
for(int i = 0; i < index; i++) {
if(a[i] != a[index - i - 1]) {
flag = true;
break;
}
}
if(!flag) {
printf("YES\n");
} else {
printf("NO\n");
}
}
return 0;
}
遞增二叉樹
這題題目也是很好懂得,讓你判斷一顆二叉樹是不是一顆遞增二叉樹(就是上一層的結點值得和要小於等於下一層的結點值得和)
思路很簡單:就是根據結點值和編號構建二叉樹,然後層次遍歷二叉樹,遍歷完每一層坐下判斷即可。
…但是讓我寫代碼我就哭了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int size = 1001;
//樹結點結構
struct node {
int val;
node *left, *right;
node(int v,node *l=NULL,node *r=NULL):val(v),left(l),right(r){}
};
//二叉樹層序遍歷
bool judge(node *root) {
if(root == NULL) {
return false;
}
queue<node* > q;
q.push(root);
int curSum = 0;
int preSum = 0;
while(!q.empty()) {
curSum = 0;
int len = q.size();
for(int i = 0; i < len; i++) {
node *temp = q.front();
curSum += temp->val;
q.pop();
if(temp->left != NULL) {
q.push(temp->left);
}
if(temp->right != NULL) {
q.push(temp->right);
}
if(curSum < preSum) {
return false;
}
preSum = curSum;
}
}
return true;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
vector<node* > data;
int *in = new int[n];
//初始化
for(int i = 0; i < n; i++) {
node *temp = new node(-1);
data.push_back(temp);
in[i] = 0;
}
//構建二叉樹
for(int i = 0; i < n; i++) {
int v, l, r;
scanf("%d %d %d", &v, &l, &r);
in[l] = in[r] = 1;
data[i]->val = v;
if(l != -1) {
data[i]->left = data[l];
}
if(r != -1) {
data[i]->right = data[r];
}
}
int r = 0;
for(int i = 0; i < n; i++) {
//找到根節點
if(in[i] == 0) {
r = i;
break;
}
}
if(judge(data[r])) {
printf("YES\n");
} else {
printf("NO\n");
}
}
return 0;
}
喝咖啡
這題主要是考察思維邏輯的嚴謹性,注意邊界問題等
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int t, k, m;
cin >> t;
while(t--) {
cin >> k >> m;
vector<int> data;
data.push_back(1 - k - 1);
for(int i = 0; i < m; i++) {
int x;
cin >> x;
data.push_back(x);
}
data.push_back(30 + k + 1);
int res = m;
for(int i = 0; i < data.size() - 1; i++) {
int diff = data[i + 1] - data[i] - 1;
if(diff > k) {
res += (diff - k) / (k + 1);
}
}
cout << res << endl;
}
return 0;
}