1187. 使数组严格递增(2020.3.25)
题意:
给你两个整数数组 arr1 和 arr2,返回使 arr1 严格递增所需要的最小「操作」数(可能为 0)。
每一步「操作」中,你可以分别从 arr1 和 arr2 中各选出一个索引,分别为 i 和 j,0 <= i < arr1.length 和 0 <= j < arr2.length,然后进行赋值运算 arr1[i] = arr2[j]。
如果无法让 arr1 严格递增,请返回 -1。
数据范围:
1 <= arr1.length, arr2.length <= 2000
0 <= arr1[i], arr2[i] <= 10^9
思路:
因为只需要比大小,所以离散化。
观察到数据范围只有2000,令d(i,j)表示前i个数保持严格递增,且结尾为j的最少操作次数,两层循环dp一下就行了。
代码顺便优化了一下空间。
code:
class Solution {
public:
int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
int n=arr1.size(),m=arr2.size();
//离散化
sort(arr2.begin(),arr2.end());
m=unique(arr2.begin(),arr2.end())-arr2.begin();
vector<int>temp;
for(int i=0;i<n;i++)temp.push_back(arr1[i]);
for(int i=0;i<m;i++)temp.push_back(arr2[i]);
sort(temp.begin(),temp.end());
int num=unique(temp.begin(),temp.end())-temp.begin();
for(int i=0;i<n;i++)arr1[i]=lower_bound(temp.begin(),temp.begin()+num,arr1[i])-temp.begin();
for(int i=0;i<m;i++)arr2[i]=lower_bound(temp.begin(),temp.begin()+num,arr2[i])-temp.begin();
//dp
int d[2][num],pos=0;
for(int i=0;i<2;i++)for(int j=0;j<num;j++)d[i][j]=1e9;
for(int i=0;i<m;i++)d[pos][arr2[0]]=1;
d[pos][arr1[0]]=0;
for(int i=1;i<n;i++){
pos^=1;
for(int j=0;j<num;j++)d[pos][j]=1e9;
for(int j=0;j<arr1[i];j++){//不换
d[pos][arr1[i]]=min(d[pos][arr1[i]],d[pos^1][j]);
}
int mi=1e9;
int k=0;
for(int j=0;j<m;j++){//换
while(k<num&&k<arr2[j]){
mi=min(mi,d[pos^1][k]);
k++;
}
d[pos][arr2[j]]=min(d[pos][arr2[j]],mi+1);
}
}
//ans
int ans=1e9;
for(int i=0;i<num;i++){
ans=min(ans,d[pos][i]);
}
if(ans==1e9)ans=-1;
return ans;
}
};
139. 单词拆分(2020.3.26)
题意:
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
思路:
令d(i)表示位置i前面是否能加空格,如果存在j<i且d(j)=1且s.substr(j,i)是单词,则d(i)=1
这样就得到了一个O(n2)的算法。
考虑到只需要找满足d(j)=1的j,d(j)=0的可以忽略,因此用vector存d(j)=1的j,优化了第二维。
代码里面还有乱七八糟的小优化
code:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
map<string,bool>mark;
int maxsize=0;
for(string i:wordDict){
mark[i]=1;
int len1=i.size();
if(len1>maxsize)maxsize=len1;
}
vector<int>d;
d.push_back(0);
int len=s.size();
for(int i=1;i<=len;i++){
//随便优化一下
while(!d.empty()&&i-d[0]>maxsize)d.erase(d.begin());
if(d.empty())return 0;
//
for(int v:d){
if(i-v>maxsize)continue;
if(mark.count(s.substr(v,i-v))){
d.push_back(i);
break;
}
}
}
return *(d.end()-1)==len;
}
};
76. 最小覆盖子串(2020.3.27)
题意:
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
code:
class Solution {
public:
string minWindow(string s, string t) {
//处理
int len=s.size();
int mark[128]={0},sz=0;
for(char v:t){
mark[v]++;
if(mark[v]==1)sz++;
}
//计算
int milen=len+1;
int pos=-1;
int now[128]={0};
int sum=0;
int l=0;
for(int i=0;i<len;i++){
now[s[i]]++;
if(now[s[i]]==mark[s[i]])sum++;
while(sum==sz){
int nowlen=i-l+1;
if(nowlen<milen){
milen=nowlen;
pos=l;
}
now[s[l]]--;
if(now[s[l]]==mark[s[l]]-1)sum--;
l++;
}
}
if(pos==-1)return "";
else return s.substr(pos,milen);
}
};
72. 编辑距离(2020.3.28)
题意:
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
1.插入一个字符
2.删除一个字符
3.替换一个字符
code:
class Solution {
public:
int minDistance(string word1, string word2) {
int n=word1.size(),m=word2.size();
int d[n+1][m+1];
for(int i=0;i<=n;i++){
d[i][0]=i;
}
for(int j=0;j<=m;j++){
d[0][j]=j;
}
for(int j=1;j<=m;j++){
for(int i=1;i<=n;i++){
if(word2[j-1]==word1[i-1]){//dp数组下标从1开始,所以这里是i-1和j-1
d[i][j]=d[i-1][j-1];
}else{
d[i][j]=min(min(d[i-1][j],d[i][j-1]),d[i-1][j-1])+1;
}
}
}
return d[n][m];
}
};
/*
d[i][j]表示s串前i个字符变为t串前j个字符的最小操作次数
如果s[i]==t[i],那么d[i][j]=d[i-1][j-1]
如果s[i]!=t[i],那么:
增加:d[i][j-1]+1
删除:d[i-1][j]+1
替换:d[i-1][j-1]+1
对上面三者取min来更新d[i][j]
*/
629. K个逆序对数组(2020.3.29)
题意:
给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。
逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。
由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。
code:
class Solution {
public:
int kInversePairs(int n, int k) {
const int mod=1e9+7;
int d[n+1][k+1];
for(int i=0;i<=n;i++){//初始化
d[i][0]=1;
for(int j=1;j<=k;j++)d[i][j]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
//d[i][j]=d[i][j-1]-d[i-1][(j-1)-(i-1)]+d[i-1][j]
d[i][j]=d[i][j-1]+d[i-1][j];
if(j-i>=0)d[i][j]-=d[i-1][j-i];
d[i][j]=(d[i][j]%mod+mod)%mod;
}
}
return d[n][k];
}
};
/*
d[i][j]表示1到i组成逆序对数量为j的方案数
如果计算出前i-1的答案,考虑将i插入前i-1中:
因为此时i是最大的数,插入位置后面有多少个数,逆序对就增加多少
插入到最后的时候,逆序对增加0,则d[i][j]+=d[i-1][j]
插入到倒数第二个位置的时候,逆序对增加1,则d[i][j]+=d[i-1][j-1]
...
插入到头部的时候,逆序对增加i-1,则d[i][j]+=d[i-1][j-(i-1)]
那么转移方程为:
d[i][j]=d[i-1][j]+d[i-1][j-1]+...+d[i-1][j-(i-1)]
三层循环i,j,k,这样就有了一个复杂度为O(n*k^2)的算法
观察到n<=1e3,k<=1e3,这样的复杂度不能满足要求,考虑优化
d[i][j]=d[i-1][j]+d[i-1][j-1]+...+d[i-1][j-(i-1)]
d[i][j-1]=d[i-1][j-1]+...+d[i-1][(j-1)-(i-1)]
两者之间有重复计算的部分,因此可推出:
d[i][j]=d[i][j-1]-d[i-1][(j-1)-(i-1)]+d[i-1][j]
这样复杂度就变为O(n*k)了
*/
1012. 至少有 1 位重复的数字(2020.3.30)
题意:
给定正整数 N,返回小于等于 N 且具有至少 1 位重复数字的正整数。
code:
class Solution {
public:
int digit[11];
int d[11][1<<10][2];
int dfs(int len,bool limit,bool pre,int sta,bool ok){
if(!len)return ok;
if(!limit&&!pre&&d[len][sta][ok]!=-1)return d[len][sta][ok];
int ans=0;
int ma=(limit?digit[len]:9);
for(int i=0;i<=ma;i++){
if(pre&&i==0){//前导0
ans+=dfs(len-1,limit&&i==ma,1,sta,ok);
}else{
ans+=dfs(len-1,limit&&i==ma,0,sta|(1<<i),ok||(sta)&(1<<i));
}
}
if(!limit&&!pre)d[len][sta][ok]=ans;
return ans;
}
int solve(int n){
int len=0;
while(n){
digit[++len]=n%10;
n/=10;
}
return dfs(len,1,1,0,0);
}
int numDupDigitsAtMostN(int N) {
memset(d,-1,sizeof d);
return solve(N);
}
};
/*
数位dp,我也不知道记忆化的时候要记录哪些变量,乱写一下就过了
*/
793. 阶乘函数后K个零(2020.3.31)
题意:
f(x) 是 x! 末尾是0的数量。(回想一下 x! = 1 * 2 * 3 * … * x,且0! = 1)
例如, f(3) = 0 ,因为3! = 6的末尾没有0;而 f(11) = 2 ,因为11!= 39916800末端有2个0。给定 K,找出多少个非负整数x ,有 f(x) = K 的性质。
code:
class Solution {
public:
long long cal(long long n,int x){
long long ans=0;
while(n){
ans+=n/x;
n/=x;
}
return ans;
}
long long check(long long mid){
return min(cal(mid,2),cal(mid,5));
}
int preimageSizeFZF(int K) {
long long l=0,r=1e10;
long long mi;
while(l<=r){
long long mid=(l+r)/2;
long long t=check(mid);
if(t>=K)mi=mid,r=mid-1;
else l=mid+1;
}
l=mi,r=1e10;
long long ma;
while(l<=r){
long long mid=(l+r)/2;
long long t=check(mid);
if(t>=K+1)ma=mid,r=mid-1;
else l=mid+1;
}
return ma-mi;
}
};
/*
二分出最小值和最大值即可,注意用longlong
*/
面试题 17.19. 消失的两个数字(2020.4.1)
题意:
给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?
以任意顺序返回这两个数字均可。
code1:
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
int sum=0;
int n=nums.size()+2;
for(int v:nums)sum+=v;
int two=n*(n+1)/2-sum;//两个数的和
n=two/2;//其中一个数肯定是two的一半
sum=n*(n+1)/2;
for(int v:nums)if(v<=n)sum-=v;
return vector<int>{two-sum,sum};
}
};
/*
n=nums.size()+2;
缺少的两个数的和two=n*(n+1)/2-sum(nums[])
缺少的两个数一定是一个小于等于two/2,一个大于two/2
令k=two/2;
那么计算一遍数组中小于等于k的数的和sum2
那么k*(k+1)-sum2就是其中一个缺少的数p1
那么另外一个数p2=two-p1
*/
code2:
待
956. 最高的广告牌(2020.4.2)
题意:
你正在安装一个广告牌,并希望它高度最大。这块广告牌将有两个钢制支架,两边各一个。每个钢支架的高度必须相等。
你有一堆可以焊接在一起的钢筋 rods。举个例子,如果钢筋的长度为 1、2 和 3,则可以将它们焊接在一起形成长度为 6 的支架。
返回广告牌的最大可能安装高度。如果没法安装广告牌,请返回 0。
code:
class Solution {
public:
int tallestBillboard(vector<int>& rods) {
int len=rods.size();
int sum=0;
for(int v:rods)sum+=v;
int d[2][sum+1];
int pos=0;
for(int i=0;i<=sum;i++)d[pos][i]=0;
for(int i=1;i<=len;i++){
pos^=1;
for(int j=0;j<=sum;j++)d[pos][j]=d[pos^1][j];
int v=rods[i-1];
for(int j=0;j<=sum;j++){
if(d[pos^1][j]<j)continue;//差值为j则长度至少为j,否则不成立,跳过
if(j+v<=sum)d[pos][j+v]=max(d[pos][j+v],d[pos^1][j]+v);
d[pos][abs(j-v)]=max(d[pos][abs(j-v)],d[pos^1][j]+v);
}
}
return d[pos][0]/2;
}
};
/*
d[i][j]为前i项差值为j的最大长度,转移方程
d[i][j+v]=max(d[i][j+v],d[i-1][j]+v);
d[i][abs(j-v)]=max(d[i][abs(j-v)],d[i-1][j]+v);
*/