2022-03-03每日刷题打卡
力扣——每日一题
258. 各位相加
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。
示例 1:
输入: num = 38
输出: 2
解释: 各位相加的过程为:
38 --> 3 + 8 --> 11
11 --> 1 + 1 --> 2
由于 2 是一位数,所以返回 2。
提示:
0 <= num <= 2^31 - 1
**进阶:**你可以不使用循环或者递归,在 O(1)
时间复杂度内解决这个问题吗?
非进阶做法:两个while嵌套,内部的while每次获取num各个位上的数并加起来,如果加起来后的数大于等于10,就继续回头循环。外面那层while就是判断num是否是一位数。
class Solution {
public:
int addDigits(int num) {
int res=0;
while(num)
{
while(num)
{
res+=num%10;
num/=10;
}
if(res>=10)num=res,res=0;
}
return res;
}
};
进阶做法:首先我们知道,对于这样一个数abcd,它等于a*10^3+b *10^2+c 10^1+d 10^0。我们要获得的是各位数之和,即a+b+c+d,两者之间的差值就是a 999+b * 99+c9,此时可以发现,差值都是9的倍数,即对9取模后等于0。既然知道差值对9取余会变成0,那我们不如就直接对num%9,这样就变成(a+999 * a+b+99 * b+9 c+d)%9,可以化为(a%9+(999 * a)%9+b%9+(b 99)%9+c%9+(c *9)%9+d%9)%9,这样得到的就是a+b+c+d了。而且不用担心会大于等于10,因为我们计算总和后我们仍然会对他进行取模。要注意的是,有1点特殊情况,如果遇到9的倍数,比如9,不能对它直接取模9,因为9它已经是一位数了,如果取模得到的就是0,所以要特殊处理一下 ,当num为9的倍数时直接返回9。
class Solution {
public:
int addDigits(int num) {
if(num%9==0&&num)return 9;
return num%9;
}
};
代码源——div1每日一题
序列操作 - 题目 - Daimayuan Online Judge
给定一个长度为 n 的序列 a1,a2,…,an。
你需要进行两种操作:
1、1 x y——将第 x 个数变为 y;
2、2 y——将所有小于 y 的数修改为 y;
共执行 q 次操作,输出执行完所有操作后的序列。
输入格式
第一行两个数字 n , q (1≤n,q≤10^6)。
接下来一行 n 个整数 a1,a2,…,ana (0≤a≤10^9)。
接下来 q 行,每行表示一个操作: 1 x y 或 2 y (1≤x≤n,0≤y≤10^9)。
输出格式
一行整数,表示操作完后的序列,用空格分隔。
样例输入
5 5
3 6 14 16 12
2 13
2 16
1 1 1
1 2 14
2 11
样例输出
11 14 16 16 16
用一个数组v把元素都存下来,首先我们看2操作,它是把所有小于y的数都变成y,那么我们其实只用看所有2操作中最大的那个y就可以了,不管先后,我们只要记录最大的那个y,等所有操作结束后遍历数组,把小于y的变成y即可。其次是操作1,操作1有点问题,因为1操作是强制修改,所以哪怕已经被二操作改成y了,我还是可以通过1操作把值改成较小的,比如2 16,1 1 1,这样下来第一个数还是1,但要是根据我们前面说的,记录最大的y等所有操作结束后再变,这样会使得第一个数变成16,为解决这个问题我们要进行两步措施,一是准备一个时间数组times,用来记录每个数被操作1修改的时间(第几布操作被修改),二是我们把所以操作先记下来,然后从尾往头开始进行修改。从尾往头开始遍历,当遇到2操作时,判断这个2操作的y值是否比我们当前记录的要大(初始可以记录一个很小的数),如果是,就更新记录的值并把这个操作的时间记录下来。如果遇到1操作,则给数组v的元素进行修改,修改的值为当前1操作的y值和我们记录的最大值中的max,因为我们是从后往前计算的,如果是操作1的大,那么即时被修改了,后面的2操作里也没有能大过它的数,如果是操作2的大,那不管此时1操作修改成什么数了,后面的操作2也会把它修改。我们修改完后,在数组times里对应的位置上记录操作1的时间。我们每个位置只记录这一次时间,因为哪怕之和我们再遇到修改这个位置的操作1,那也是靠后面的操作1说的算,不管你前面修改的怎么样,最后也会被后面的修改。按照如上操作从尾往头遍历完所有操作后就可以开始输出数组了,如果将要输出的数小于我们记录的最大值,且时间也比我们最大值的操作小(如果这个位置没有被操作1修改过,时间记录为-1),那就把这个元素变成最大值后输出,如果时间比最大值的大,就原样输出。
(这题一开始交还超时了,后来把cout和cin改成scanf和printf就过了)
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<set>
#include<string>
#include<map>
#include<unordered_map>
#include<stack>
typedef long long ll;
int main()
{
int n, m;
scanf("%d %d", &n, &m);
vector<ll>v(n), times(n, -1);
vector<vector<ll>>st;
for (int i = 0; i < n; i++)scanf("%lld", &v[i]);
for (int i = 0; i < m; i++)
{
int ans;
scanf("%d", &ans);
if (ans == 1)
{
vector<ll>v1(3);
v1[0] = ans;
scanf("%lld %lld", &v1[1], &v1[2]);
st.push_back(v1);
}
else
{
vector<ll>v1(2);
v1[0] = ans;
scanf("%lld ", &v1[1]);
st.push_back(v1);
}
}
ll res = -1, time = -1;
for (int i = m - 1; i >= 0; i--)
{
if (st[i][0] == 2 && res < st[i][1])
{
res = st[i][1];
time = (long long)i;
}
else if (st[i][0] == 1 && times[st[i][1] - 1] == -1)
{
v[st[i][1] - 1] = max(st[i][2], res);
times[st[i][1] - 1] = (long long)i;
}
}
for (int i = 0; i < n; i++)
{
if (v[i] < res && time >= times[i])printf("%lld ", res);
else printf("%lld ", v[i]);
}
return 0;
}
订单编号 - 题目 - Daimayuan Online Judge
小缘开了一家公司,生意很好,每天都会收到很多订单,自动交易系统会自动给这些订单生成没有重复的订单编号。但是有一天,系统出现了未知的错误,导致当天的订单编号可能有重复的,这可把小缘急坏了。你可以帮助小缘按照规则给这些订单重新编号吗?
按照时间先后顺序给出 N 个正整数作为原订单编号,你需要按照规则依次赋予这些订单新的编号,对于任意一个订单,要找到大于等于其原订单编号且未被使用过的(没有被之前的订单作为新的订单编号)的最小整数,作为它的新订单编号。
例如: 原订单编号依次为1 2 3 1,则新订单编号应该为1 2 3 4 (前3个订单的原订单编号都没有使用过,所以用其原订单编号即可,对于第四个订单,原订单编号为1,而1, 2, 3都已经被使用过,所以新订单编号为4)。
输入格式
第一行输入一个整数 N (1≤N≤5×10^5)
第二行输入 N 个数 ai (1≤ai≤10^9) 作为原订单编号。
输出格式
输出一行,包含 N 个整数为新的订单编号。
样例输入1
6
2 3 4 1 1 1
样例输出1
2 3 4 1 5 6
这题我们用个区间分割的方式来解,一开始准备一个区间,区间用数对pair表示,first是右区间,second是左区间(是的,反过来,至于为什么等下说),根据题目给的数据,我们可以开一个{2e9,1}的区间,把它存入set容器里,每次根据读入的编号来分割区间,比如样例第一个元素是2,我们就以2来分割左右区间,这样就可以分成{1,1}和{2e9,3}两个区间,为了快速找到编号所在的区间,我们可以用lower_found()函数来找,它的作用是找到数组里第一个不大于目标值的元素,用set并且把左右区间调换也是为了这一点,set的自动排序功能可以使得小区间永远排在前面,pair左右区间调换是为了把右区间较小的排在前面,set的排序遇到pair时,会先根据first来排序,如果相等就根据second排序。按照样例,输出完2后区间会分成{1,1}和{2e9,3},下一个数是3时就会变成{1,1}{2,3}{2e9,4},但{2,3}这一区间是非法的(别忘了前面的是右区间,右区间的数怎么能比左区间小呢)所以我们也要注意,如果分完后有区间出现左区间的数大于右区间,那就把这个数对删除。按照此情况继续输出完4和1后,set中区间只剩{2e9,5}了,此时读到编号为1,我们用lower_found找第一个不小于1的数,那就只能是这个{2e9,5}了,我们输出5,然后继续分割区间{2e9,6}……由此,便可以得到所有新的订单编号了。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<set>
#include<string>
#include<map>
#include<unordered_map>
#include<stack>
typedef long long ll;
typedef pair<int, int>PII;
set<PII>s;
void myinsert(int l, int r)
{
if (l > r)return;
s.insert({ r,l });
}
int main()
{
int n;
cin >> n;
s.insert({ 2e9,1 });
for (int i = 1; i <= n; i++)
{
int x;
scanf("%d",&x);
auto it = s.lower_bound({ x,0 });
if (it->second <= x)
{
cout << x << " ";
myinsert(it->second, x - 1);
myinsert(x + 1, it->first);
s.erase(it);
}
else
{
cout << it->second << " ";
myinsert(it->second + 1, it->first);
s.erase(it);
}
}
return 0;
}
一本通——动态规划
1306:最长公共子上升序列
【题目描述】
给定两个整数序列,写一个程序求它们的最长上升公共子序列。
当以下条件满足的时候,我们将长度NN的序列S1,S2,…,SN称为长度为M的序列A1,A2,…,AM的上升子序列:
存在1≤i1<i2<…<iN≤M,使得对所有1≤j≤N,均有Sj=Aij,且对于所有的1≤j<N,均有Sj<Sj+1。
【输入】
每个序列用两行表示,第一行是长度M(1≤M≤500),第二行是该序列的M个整数Ai(−231<=Ai<231)
【输出】
在第一行,输出两个序列的最长上升公共子序列的长度L。在第二行,输出该子序列。如果有不止一个符合条件的子序列,则输出任何一个即可。
【输入样例】
5
1 4 2 5 -12
4
-12 1 2 4
【输出样例】
2
1 4
先求两个数组的最长公共子序列,再求这个最长公共子序列的最长上升序列。
(我这写法有点乱,但能AC啊)
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<set>
#include<string>
#include<map>
#include<unordered_map>
#include<stack>
typedef long long ll;
typedef pair<int, int>PII;
const int N = 550;
ll f[N][N], a[N], b[N];
int main()
{
int n, m;
cin >> n ;
for (int i = 1; i <= n; i++)cin >> a[i];
cin >> m;
for (int i = 1; i <= m; i++)cin >> b[i];
ll res = -1;
for(int i=1;i<=n;i++)
for (int j = 1; j <= n; j++)
{
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j])f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
res = max(res, f[i][j]);
}
vector<ll>v;
int l = n, r = m;
while (res)
{
int x = -1, y = -1 ;
for (int i = r; i > 0; i--)
{
if (f[l][i] != f[l][i - 1] && f[l][i] == res)
{
y = i;
}
}
for (int i = l; i > 0; i--)
if (f[i][y] != f[i - 1][y] && f[i][y] == res)
x = i;
v.push_back(b[y]);
l = x - 1, r = y - 1;
res--;
}
reverse(v.begin(), v.end());
int len = v.size(),ans;
vector<int>dp(len + 1, 1), h(len + 1,-1);
for (int i = 0; i < v.size(); i++)
{
for (int j = i - 1; j >= 0; j--)
{
if (v[i] > v[j] && dp[i] < dp[j] + 1)
{
dp[i] = dp[j] + 1, h[i] = j;
}
}
if (res < dp[i])
{
res = dp[i];
ans = i;
}
if (h[i] == -1)h[i] = i;
}
cout << res << endl;
vector<int>math;
while (h[ans] != ans)
{
math.push_back(v[ans]);
ans = h[ans];
}
math.push_back(v[ans]);
for (int i = math.size()-1; i >=0 ; i--)cout << math[i] << " ";
return 0;
}
1276:【例9.20】编辑距离
【题目描述】
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符。
对任意的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数。
【输入】
第一行为字符串A;第二行为字符串B;字符串A和B的长度均小于2000。
【输出】
只有一个正整数,为最少字符操作次数。
【输入样例】
sfdqxbw
gfdgw
【输出样例】
4
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<set>
#include<string>
#include<map>
#include<unordered_map>
#include<stack>
typedef long long ll;
typedef pair<int, int>PII;
const int N = 2010;
ll f[N][N];
int main()
{
string str1, str2;
cin >> str1 >> str2;
int n = str1.size(), m = str2.size();
for (int i = 0; i <= n; i++)f[i][0] = i;
for (int i = 0; i <= m; i++)f[0][i] = i;
for(int i=1;i<=n;i++)
for (int j = 1; j <= m; j++)
{
f[i][j] = min(f[i - 1][j], f[i][j - 1])+1;
f[i][j] = min(f[i][j], str1[i - 1] != str2[j - 1] ? f[i - 1][j - 1] + 1 : f[i - 1][j - 1]);
}
cout << f[n][m];
return 0;
}