目录

​1414. 和为 K 的最少斐波那契数字数目​

​1419. 数青蛙​

​1420. 生成数组​

​1424. 对角线遍历 II​

​1441. 用栈操作构建数组​

​1442. 形成两个异或相等数组的三元组数目​

​1443. 收集树上所有苹果的最少时间​

​1450. 在既定时间做作业的学生人数​

​1451. 重新排列句子中的单词​

​1452. 收藏清单​

​1455. 检查单词是否为句中其他单词的前缀​

​1456. 定长子串中元音的最大数目​

​1457. 二叉树中的伪回文路径​

​1458. 两个子序列的最大点积​

​1461. 检查一个字符串是否包含所有长度为 K 的二进制子串​

64.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%A4%E5%85%83%E7%B4%A0%E7%9A%84%E6%9C%80%E5%A4%A7%E4%B9%98%E7%A7%AF">​1464. 数组中两元素的最大乘积​

​1465. 切割后面积最大的蛋糕​

​1466. 重新规划路线​

​1467. 两个盒子中球的颜色数相同的概率​

​1470. 重新排列数组​

​1471. 数组中的 k 个最强值​

​1472. 设计浏览器历史记录​

​1473. 给房子涂色 III​

​1480. 一维数组的动态和​

​1481. 不同整数的最少数目​

​1482. 制作 m 束花所需的最少天数​

​1483. 树节点的第 K 个祖先​

​1486. 数组异或操作​

​1487. 保证文件名唯一​

​1488. 避免洪水泛滥(min型线段树)​

​1496. 判断路径是否相交​

​1502. 判断能否形成等差数列​

​1503. 所有蚂蚁掉下来前的最后一刻​

​1504. 统计全 1 子矩形​

​1512. 好数对的数目​

​1513. 仅含 1 的子串数​



1414. 和为 K 的最少斐波那契数字数目

给你数字 k ,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。

斐波那契数字定义为:

F1 = 1

F2 = 1

Fn = Fn-1 + Fn-2 , 其中 n > 2 。

数据保证对于给定的 k ,一定能找到可行解。


示例 1:

输入:k = 7

输出:2 

解释:斐波那契数字为:1,1,2,3,5,8,13,……

对于 k = 7 ,我们可以得到 2 + 5 = 7 。

示例 2:

输入:k = 10

输出:2 

解释:对于 k = 10 ,我们可以得到 2 + 8 = 10 。

示例 3:

输入:k = 19

输出:3 

解释:对于 k = 19 ,我们可以得到 1 + 5 + 13 = 19 。


提示:

1 <= k <= 10^9

int findMinFibonacciNumbers(int k){
int f[45];
f[0] = 0, f[1] = 1;
for (int i = 2; i <= 44; i++)f[i] = f[i - 1] + f[i - 2];
int ans=0;
for(int i=44;i>0;i--)if(k>=f[i])
{
k-=f[i],ans++;
}
return ans;
}

1419. 数青蛙

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak" )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak” 。请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

注意:要想发出蛙鸣 "croak",青蛙必须 依序 输出 ‘c’, ’r’, ’o’, ’a’, ’k’ 这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。

如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1 。


示例 1:

输入:croakOfFrogs = "croakcroak"

输出:1 

解释:一只青蛙 “呱呱” 两次

示例 2:

输入:croakOfFrogs = "crcoakroak"

输出:2 

解释:最少需要两只青蛙,“呱呱” 声用黑体标注

第一只青蛙 "crcoakroak"

第二只青蛙 "crcoakroak"

示例 3:

输入:croakOfFrogs = "croakcrook"

输出:-1

解释:给出的字符串不是 "croak" 的有效组合。

示例 4:

输入:croakOfFrogs = "croakcroa"

输出:-1


提示:

1 <= croakOfFrogs.length <= 10^5

字符串中的字符只有 'c', 'r', 'o', 'a' 或者 'k'

class Solution {
public:
int minNumberOfFrogs(string croakOfFrogs) {
map<char,int>m;
int ans=0;
for(int i=0;i<croakOfFrogs.length();i++)
{
m[croakOfFrogs[i]]++;
if(m['c']<m['r'] || m['r']<m['o'] || m['o']<m['a'] || m['a']<m['k'])return -1;
ans=max(ans,m['c']-m['k']);
}
if(m['c']>m['r'] || m['r']>m['o'] || m['o']>m['a'] || m['a']>m['k'])return -1;
return ans;
}
};

1420. 生成数组

DP ​​javascript:void(0)​

1424. 对角线遍历 II

给你一个列表 nums ,里面每一个元素都是一个整数列表。请你依照下面各图的规则,按顺序返回 nums 中对角线上的整数。

示例 1:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook

输入:nums = [[1,2,3],[4,5,6],[7,8,9]]

输出:[1,4,2,7,5,3,8,6,9]

示例 2:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook_02

输入:nums = [[1,2,3,4,5],[6,7],[8],[9,10,11],[12,13,14,15,16]]

输出:[1,6,2,8,7,3,9,4,12,10,5,13,11,14,15,16]

示例 3:

输入:nums = [[1,2,3],[4],[5,6,7],[8],[9,10,11]]

输出:[1,4,2,5,3,8,6,9,7,10,11]

示例 4:

输入:nums = [[1,2,3,4,5,6]]

输出:[1,2,3,4,5,6]


提示:

1 <= nums.length <= 10^5

1 <= nums[i].length <= 10^5

1 <= nums[i][j] <= 10^9

nums 中最多有 10^5 个数字。


超时代码:

void f(vector<vector<int>>&ans, vector<int>&v)
{
vector<int>tmp;
ans.insert(ans.begin(), tmp);
for (int i = 0; i < v.size(); i++)
{
if (i >= ans.size())ans.push_back(tmp);
ans[i].push_back(v[i]);
}
}
class Solution {
public:
vector<int> findDiagonalOrder(vector<vector<int>>& nums) {
vector<vector<int>>ans;
for (int i = nums.size() - 1; i >= 0; i--)f(ans, nums[i]);
vector<int>res;
for (int i = 0; i < ans.size(); i++)
{
for (int j = 0; j < ans[i].size(); j++)res.push_back(ans[i][j]);
}
return res;
}
};


其中最致命的问题是,有大量的在vector前面插入元素的操作,

看了同事的js代码之后,我照着她的思路重写了代码:

class Solution {
public:
vector<int> findDiagonalOrder(vector<vector<int>>& nums) {
map<int,vector<int>>ans;
vector<int>emp;
int k=-1;
for(int i=0;i<nums.size();i++)for(int j=0;j<nums[i].size();j++)
{
if(k<i+j)ans[++k]=emp;
ans[i+j].push_back(nums[i][j]);
}
vector<int>res;
for (int i = 0; i < ans.size(); i++)
{
for (int j = ans[i].size()-1;j>=0;j--)res.push_back(ans[i][j]);
}
return res;
}
};

1441. 用栈操作构建数组

给你一个目标数组 target 和一个整数 n。每次迭代,需要从  list = {1,2,3..., n} 中依序读取一个数字。

请使用下述操作来构建目标数组 target :

Push:从 list 中读取一个新元素, 并将其推入数组中。

Pop:删除数组中的最后一个元素。

如果目标数组构建完成,就停止读取更多元素。

题目数据保证目标数组严格递增,并且只包含 1 到 n 之间的数字。

请返回构建目标数组所用的操作序列。

题目数据保证答案是唯一的。


示例 1:

输入:target = [1,3], n = 3

输出:["Push","Push","Pop","Push"]

解释: 

读取 1 并自动推入数组 -> [1]

读取 2 并自动推入数组,然后删除它 -> [1]

读取 3 并自动推入数组 -> [1,3]

示例 2:

输入:target = [1,2,3], n = 3

输出:["Push","Push","Push"]

示例 3:

输入:target = [1,2], n = 4

输出:["Push","Push"]

解释:只需要读取前 2 个数字就可以停止。

示例 4:

输入:target = [2,3,4], n = 4

输出:["Push","Pop","Push","Push","Push"]


提示:

1 <= target.length <= 100

1 <= target[i] <= 100

1 <= n <= 100

target 是严格递增的

class Solution {
public:
vector<string> buildArray(vector<int>& target, int n) {
vector<string>ans;
int key=0;
for(auto it=target.begin();it!=target.end();it++)
{
int k=*it;
key++;
while(key<k)
{
key++;
ans.insert(ans.end(),"Push");
ans.insert(ans.end(),"Pop");
}
ans.insert(ans.end(),"Push");
}
return ans;
}
};

1442. 形成两个异或相等数组的三元组数目

给你一个整数数组 arr 。

现需要从数组中取三个下标 i、j 和 k ,其中 (0 <= i < j <= k < arr.length) 。

a 和 b 定义如下:

a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1]

b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k]

注意:^ 表示 按位异或 操作。

请返回能够令 a == b 成立的三元组 (i, j , k) 的数目。


示例 1:

输入:arr = [2,3,1,6,7]

输出:4

解释:满足题意的三元组分别是 (0,1,2), (0,2,2), (2,3,4) 以及 (2,4,4)

示例 2:

输入:arr = [1,1,1,1,1]

输出:10

示例 3:

输入:arr = [2,3]

输出:0

示例 4:

输入:arr = [1,3,5,7,9]

输出:3

示例 5:

输入:arr = [7,11,12,9,5,2,7,17,22]

输出:8


提示:

1 <= arr.length <= 300

1 <= arr[i] <= 10^8

class Solution {
public:
int countTriplets(vector<int>& arr) {
int len=arr.size();
int ans=0;
for(int i=1;i<len;i++)
{
arr[i]^=arr[i-1];
if(arr[i]==0)ans+=i;
}
for(int i=0;i<len;i++)for(int k=i+1;k<len;k++)
if(arr[i]==arr[k])ans+=k-i-1;
return ans;
}
};

1443. 收集树上所有苹果的最少时间

给你一棵有 n 个节点的无向树,节点编号为 0 到 n-1 ,它们中有一些节点有苹果。通过树上的一条边,需要花费 1 秒钟。你从 节点 0 出发,请你返回最少需要多少秒,可以收集到所有苹果,并回到节点 0 。

无向树的边由 edges 给出,其中 edges[i] = [fromi, toi] ,表示有一条边连接 from 和 toi 。除此以外,还有一个布尔数组 hasApple ,其中 hasApple[i] = true 代表节点 i 有一个苹果,否则,节点 i 没有苹果。

示例 1:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook_03

输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,true,true,false]

输出:8 

解释:上图展示了给定的树,其中红色节点表示有苹果。一个能收集到所有苹果的最优方案由绿色箭头表示。

示例 2:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_i++_04

输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,false,true,false]

输出:6

解释:上图展示了给定的树,其中红色节点表示有苹果。一个能收集到所有苹果的最优方案由绿色箭头表示。

示例 3:

输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,false,false,false,false,false]

输出:0


提示:

1 <= n <= 10^5

edges.length == n-1

edges[i].length == 2

0 <= fromi, toi <= n-1

fromi < toi

hasApple.length == n

class Solution {
public:
int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
int *fa=new int[n];
int *ans=new int[n];
for(int i=0;i<n;i++)ans[i]=0;
ans[0]=1;
for(auto it=edges.begin();it!=edges.end();it++)fa[(*it)[1]]=(*it)[0];
fa[0]=0;
for(int i=0;i<n;i++)if(hasApple[i])
{
int j=i;
while(ans[j]==0)
{
ans[j]=1,j=fa[j];
}
}
int res=-1;
for(int i=0;i<n;i++)if(ans[i])res++;
return res*2;
}
};

1450. 在既定时间做作业的学生人数

给你两个整数数组 startTime(开始时间)和 endTime(结束时间),并指定一个整数 queryTime 作为查询时间。

已知,第 i 名学生在 startTime[i] 时开始写作业并于 endTime[i] 时完成作业。

请返回在查询时间 queryTime 时正在做作业的学生人数。形式上,返回能够使 queryTime 处于区间 [startTime[i], endTime[i]](含)的学生人数。


示例 1:

输入:startTime = [1,2,3], endTime = [3,2,7], queryTime = 4

输出:1

解释:一共有 3 名学生。

第一名学生在时间 1 开始写作业,并于时间 3 完成作业,在时间 4 没有处于做作业的状态。

第二名学生在时间 2 开始写作业,并于时间 2 完成作业,在时间 4 没有处于做作业的状态。

第二名学生在时间 3 开始写作业,预计于时间 7 完成作业,这是是唯一一名在时间 4 时正在做作业的学生。

示例 2:

输入:startTime = [4], endTime = [4], queryTime = 4

输出:1

解释:在查询时间只有一名学生在做作业。

示例 3:

输入:startTime = [4], endTime = [4], queryTime = 5

输出:0

示例 4:

输入:startTime = [1,1,1,1], endTime = [1,3,2,4], queryTime = 7

输出:0

示例 5:

输入:startTime = [9,8,7,6,5,4,3,2,1], endTime = [10,10,10,10,10,10,10,10,10], queryTime = 5

输出:5


提示:

startTime.length == endTime.length

1 <= startTime.length <= 100

1 <= startTime[i] <= endTime[i] <= 1000

1 <= queryTime <= 1000

class Solution {
public:
int busyStudent(vector<int>& startTime, vector<int>& endTime, int queryTime) {
int ans=0;
for(int i=0;i<startTime.size();i++)
if(startTime[i]<=queryTime && queryTime<=endTime[i])ans++;
return ans;
}
};

1451. 重新排列句子中的单词

「句子」是一个用空格分隔单词的字符串。给你一个满足下述格式的句子 text :

句子的首字母大写

text 中的每个单词都用单个空格分隔。

请你重新排列 text 中的单词,使所有单词按其长度的升序排列。如果两个单词的长度相同,则保留其在原句子中的相对顺序。

请同样按上述格式返回新的句子。


示例 1:

输入:text = "Leetcode is cool"

输出:"Is cool leetcode"

解释:句子中共有 3 个单词,长度为 8 的 "Leetcode" ,长度为 2 的 "is" 以及长度为 4 的 "cool" 。

输出需要按单词的长度升序排列,新句子中的第一个单词首字母需要大写。

示例 2:

输入:text = "Keep calm and code on"

输出:"On and keep calm code"

解释:输出的排序情况如下:

"On" 2 个字母。

"and" 3 个字母。

"keep" 4 个字母,因为存在长度相同的其他单词,所以它们之间需要保留在原句子中的相对顺序。

"calm" 4 个字母。

"code" 4 个字母。

示例 3:

输入:text = "To be or not to be"

输出:"To be or to be not"


提示:

text 以大写字母开头,然后包含若干小写字母以及单词间的单个空格。

1 <= text.length <= 10^5


比赛提交的代码:

struct nodes
{
string s;
int k;
};

bool cmp(nodes x, nodes y)
{
if(x.s.length()==y.s.length())return x.k<y.k;
return x.s.length()<y.s.length();
}

class Solution {
public:
string arrangeWords(string text) {
text[0]+='a'-'A';
vector<nodes>v;
v.clear();
int low=0,key=0;
for(int i=0;i<=text.length();i++)
{
if(i==text.length() || text[i]==' ')
{
nodes nod;
nod.s=text.substr(low,i-low);
nod.k=key++;
v.insert(v.end(),nod);
low=i+1;
}
}
sort(v.begin(),v.end(),cmp);
string ans=v[0].s;
for(int i=1;i<v.size();i++)
{
ans+=" ";
ans+=v[i].s;
}
ans[0]+='A'-'a';
return ans;
}
};

几个月后,利用自己的模板重写的代码:

//拓展数据域,加上id
template<typename T>
vector<pair<T,int>>expand(vector<T>v)
{
vector<pair<T,int>>ans;
ans.resize(v.size());
for(int i=0;i<v.size();i++)ans[i].first=v[i],ans[i].second=i;
return ans;
}
//提取pair数组的first
template<typename T1,typename T2>
vector<T1> fdraw(vector<pair<T1,T2>>v)
{
vector<T1>ans(v.size());
for(int i=0;i<v.size();i++)ans[i]=v[i].first;
return ans;
}
//给vector拓展,加上id并排序
template<typename T>
bool cmp(pair<T, int> x, pair<T, int> y)
{
if(x.first.length()==y.first.length())return x.second<y.second;
return x.first.length()<y.first.length();
}
template<typename T>
vector<pair<T, int>> sortWithId(vector<T>v)
{
vector<pair<T, int>>ans = expand(v);
sort(ans.begin(), ans.end(),cmp<T>);
return ans;
}

vector<string> stringSplit(string text)
{
vector<string>v;
v.clear();
int low=0,key=0;
for(int i=0;i<=text.length();i++)
{
if(i==text.length() || text[i]==' ')//分隔符
{
v.insert(v.end(),text.substr(low,i-low));
low=i+1;
}
}
return v;
}

class Solution {
public:
string arrangeWords(string text) {
text[0]+='a'-'A';
vector<string>v=fdraw(sortWithId(stringSplit(text)));
string ans=v[0];
for(int i=1;i<v.size();i++)ans+=" "+v[i];
ans[0]+='A'-'a';
return ans;
}
};

1452. 收藏清单

给你一个数组 favoriteCompanies ,其中 favoriteCompanies[i] 是第 i 名用户收藏的公司清单(下标从 0 开始)。

请找出不是其他任何人收藏的公司清单的子集的收藏清单,并返回该清单下标。下标需要按升序排列。


示例 1:

输入:favoriteCompanies = [["leetcode","google","facebook"],["google","microsoft"],["google","facebook"],["google"],["amazon"]]

输出:[0,1,4] 

解释:

favoriteCompanies[2]=["google","facebook"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 的子集。

favoriteCompanies[3]=["google"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 和 favoriteCompanies[1]=["google","microsoft"] 的子集。

其余的收藏清单均不是其他任何人收藏的公司清单的子集,因此,答案为 [0,1,4] 。

示例 2:

输入:favoriteCompanies = [["leetcode","google","facebook"],["leetcode","amazon"],["facebook","google"]]

输出:[0,1] 

解释:favoriteCompanies[2]=["facebook","google"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 的子集,因此,答案为 [0,1] 。

示例 3:

输入:favoriteCompanies = [["leetcode"],["google"],["facebook"],["amazon"]]

输出:[0,1,2,3]


提示:

1 <= favoriteCompanies.length <= 100

1 <= favoriteCompanies[i].length <= 500

1 <= favoriteCompanies[i][j].length <= 20

favoriteCompanies[i] 中的所有字符串 各不相同 。

用户收藏的公司清单也 各不相同 ,也就是说,即便我们按字母顺序排序每个清单, favoriteCompanies[i] != favoriteCompanies[j] 仍然成立。

所有字符串仅包含小写英文字母。


代码一:

map<string,int>m[105];
class Solution {
public:
vector<int> peopleIndexes(vector<vector<string>>& favoriteCompanies) {
vector<int>ans;
for(int i=0;i<favoriteCompanies.size();i++)
{
m[i].clear();
for(int j=0;j<favoriteCompanies[i].size();j++)m[i][favoriteCompanies[i][j]]=1;
}
for(int i=0;i<favoriteCompanies.size();i++)
{
bool flag=true;
for(int j=0;flag && (j<favoriteCompanies.size());j++)
{
if(j==i)continue;
if(favoriteCompanies[i].size()>favoriteCompanies[j].size())continue;
bool flag2=true;
for(int ii=0;flag2 && (ii<favoriteCompanies[i].size());ii++)if(!m[j][favoriteCompanies[i][ii]])flag2=false;
if(flag2)flag=false;
}
if(flag)ans.insert(ans.end(),i);
}
return ans;
}
};

这是我比赛时候的代码,已经做了一部分优化了(比较列表长短),但是还是超时,超时的用例就是极限用例。

然后我考虑到,把100个列表按照长度排序,每个列表就只需要看比自己长的列表有没有覆盖自己的,把没有被覆盖的列表的ID记下来,这样,每个列表就只需要看比自己长而且没有被任何列表覆盖的列表,我花了20分钟去改,最后没来得及完成。

代码二:

struct node
{
int num;
int id;
};
bool cmp(node x,node y)
{
return x.num>y.num;
}
map<string,int>m[105];
class Solution {
public:
vector<int> peopleIndexes(vector<vector<string>>& favoriteCompanies) {
vector<node>vn;
vector<int>ans;
for(int i=0;i<favoriteCompanies.size();i++)
{
m[i].clear();
for(int j=0;j<favoriteCompanies[i].size();j++)m[i][favoriteCompanies[i][j]]=1;
node nn;
nn.num=favoriteCompanies[i].size(),nn.id=i;
vn.insert(vn.end(),nn);
}
sort(vn.begin(),vn.end(),cmp);
for(int i=0;i<vn.size();i++)
{
bool flag=true;
for(int j=0;flag && (j<i);j++)
{
bool flag2=true;
for(int ii=0;flag2 && (ii<favoriteCompanies[vn[i].num].size());ii++)if(!m[vn[j].num][favoriteCompanies[vn[i].num][ii]])flag2=false;
if(flag2)flag=false;
}
if(flag)ans.insert(ans.end(),vn[i].id);
}
sort(ans.begin(),ans.end());
return ans;
}
};

事后我的2个同事给了我思路,一个是把map改为unordered_map,

只把代码一的第1行改掉,就AC了。

另外一个思路是不用map,把每个列表的字符串排序,然后用双指针的方法判断一个列表是不是覆盖另外一个列表,他这个思路也AC了。

1455. 检查单词是否为句中其他单词的前缀

给你一个字符串 sentence 作为句子并指定检索词为 searchWord ,其中句子由若干用 单个空格 分隔的单词组成。

请你检查检索词 searchWord 是否为句子 sentence 中任意单词的前缀。

如果 searchWord 是某一个单词的前缀,则返回句子 sentence 中该单词所对应的下标(下标从 1 开始)。

如果 searchWord 是多个单词的前缀,则返回匹配的第一个单词的下标(最小下标)。

如果 searchWord 不是任何单词的前缀,则返回 -1 。

字符串 S 的 「前缀」是 S 的任何前导连续子字符串。


示例 1:

输入:sentence = "i love eating burger", searchWord = "burg"

输出:4

解释:"burg" 是 "burger" 的前缀,而 "burger" 是句子中第 4 个单词。

示例 2:

输入:sentence = "this problem is an easy problem", searchWord = "pro"

输出:2

解释:"pro" 是 "problem" 的前缀,而 "problem" 是句子中第 2 个也是第 6 个单词,但是应该返回最小下标 2 。

示例 3:

输入:sentence = "i am tired", searchWord = "you"

输出:-1

解释:"you" 不是句子中任何单词的前缀。

示例 4:

输入:sentence = "i use triple pillow", searchWord = "pill"

输出:4

示例 5:

输入:sentence = "hello from the other side", searchWord = "they"

输出:-1


提示:

1 <= sentence.length <= 100

1 <= searchWord.length <= 10

sentence 由小写英文字母和空格组成。

searchWord 由小写英文字母组成。

前缀就是紧密附着于词根的语素,中间不能插入其它成分,并且它的位置是固定的——-位于词根之前。(引用自 前缀_百度百科 )

class Solution {
public:
int isPrefixOfWord(string sentence, string searchWord) {
int key=1;
for(int i=0,j=0;i<sentence.length();)
{
if(sentence[i]==' ')i++,key++,j=0;
else if(sentence[i]==searchWord[j])
{
i++,j++;
if(j==searchWord.length())return key;
}
else
{
j=0;
while(i<sentence.length() && sentence[i]!=' ')i++;
i++,key++;
}
}
return -1;
}
};

1456. 定长子串中元音的最大数目

给你字符串 s 和整数 k 。

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

英文中的 元音字母 为(a, e, i, o, u)。


示例 1:

输入:s = "abciiidef", k = 3

输出:3

解释:子字符串 "iii" 包含 3 个元音字母。

示例 2:

输入:s = "aeiou", k = 2

输出:2

解释:任意长度为 2 的子字符串都包含 2 个元音字母。

示例 3:

输入:s = "leetcode", k = 3

输出:2

解释:"lee"、"eet" 和 "ode" 都包含 2 个元音字母。

示例 4:

输入:s = "rhythms", k = 4

输出:0

解释:字符串 s 中不含任何元音字母。

示例 5:

输入:s = "tryhard", k = 4

输出:1


提示:

1 <= s.length <= 10^5

s 由小写英文字母组成

1 <= k <= s.length

int f(char c)
{
if(c=='a'||c=='e'||c=='i'||c=='o'||c=='u')return 1;
return 0;
}

class Solution {
public:
int maxVowels(string s, int k) {
int r=0;
for(int i=0;i<k;i++)r+=f(s[i]);
int ans=r;
for(int i=k;i<s.length();i++)
{
r+=f(s[i])-f(s[i-k]);
ans=max(ans,r);
}
return ans;
}
};

1457. 二叉树中的伪回文路径

给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。

请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。


示例 1:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook_05

输入:root = [2,3,1,3,1,null,1]

输出:2 

解释:上图为给定的二叉树。总共有 3 条从根到叶子的路径:红色路径 [2,3,3] ,绿色路径 [2,1,1] 和路径 [2,3,1] 。

     在这些路径中,只有红色和绿色的路径是伪回文路径,因为红色路径 [2,3,3] 存在回文排列 [3,2,3] ,绿色路径 [2,1,1] 存在回文排列 [1,2,1] 。

示例 2:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_数组_06

输入:root = [2,1,1,1,3,null,null,null,null,null,1]

输出:1 

解释:上图为给定二叉树。总共有 3 条从根到叶子的路径:绿色路径 [2,1,1] ,路径 [2,1,3,1] 和路径 [2,1] 。

     这些路径中只有绿色路径是伪回文路径,因为 [2,1,1] 存在回文排列 [1,2,1] 。

示例 3:

输入:root = [9]

输出:1


提示:

给定二叉树的节点数目在 1 到 10^5 之间。

节点值在 1 到 9 之间。

class Solution {
public:
int f(TreeNode* root,int n)
{
n^=(1<< root->val);
if(root->right && root->left)return f(root->right,n)+f(root->left,n);
if(root->right)return f(root->right,n);
if(root->left)return f(root->left,n);
return (n&-n)==n;
}
int pseudoPalindromicPaths (TreeNode* root) {
return f(root,0);
}
};

1458. 两个子序列的最大点积

DP ​​javascript:void(0)​

1461. 检查一个字符串是否包含所有长度为 K 的二进制子串

给你一个二进制字符串 s 和一个整数 k 。

如果所有长度为 k 的二进制字符串都是 s 的子串,请返回 True ,否则请返回 False 。


示例 1:

输入:s = "00110110", k = 2

输出:true

解释:长度为 2 的二进制串包括 "00","01","10" 和 "11"。它们分别是 s 中下标为 0,1,3,2 开始的长度为 2 的子串。

示例 2:

输入:s = "00110", k = 2

输出:true

示例 3:

输入:s = "0110", k = 1

输出:true

解释:长度为 1 的二进制串包括 "0" 和 "1",显然它们都是 s 的子串。

示例 4:

输入:s = "0110", k = 2

输出:false

解释:长度为 2 的二进制串 "00" 没有出现在 s 中。

示例 5:

输入:s = "0000000001011100", k = 4

输出:false


提示:

1 <= s.length <= 5 * 10^5

s 中只含 0 和 1 。

1 <= k <= 20

class Solution {
public:
bool hasAllCodes(string s, int k) {
unordered_set<string>se;
for(int i=0;i+k<=s.length();i++)
{
se.insert(s.substr(i,k));
if(se.size()+s.length()-i-k<(1<<k))return false;
if(se.size()==(1<<k))return true;
}
return false;
}
};

1464. 数组中两元素的最大乘积

给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。

请你计算并返回该式的最大值。


示例 1:

输入:nums = [3,4,5,2]

输出:12 

解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。 

示例 2:

输入:nums = [1,5,4,5]

输出:16

解释:选择下标 i=1 和 j=3(下标从 0 开始),则可以获得最大值 (5-1)*(5-1) = 16 。

示例 3:

输入:nums = [3,7]

输出:12


提示:

2 <= nums.length <= 500

1 <= nums[i] <= 10^3

class Solution {
public:
int maxProduct(vector<int>& nums) {
int a=0,b=0,c;
for(int i=0;i<nums.size();i++)
{
c=nums[i]-1;
if(b<c)b^=c^=b^=c;
if(a<c)a^=c^=a^=c;
}
return a*b;
}
};

1465. 切割后面积最大的蛋糕

矩形蛋糕的高度为 h 且宽度为 w,给你两个整数数组 horizontalCuts 和 verticalCuts,其中 horizontalCuts[i] 是从矩形蛋糕顶部到第  i 个水平切口的距离,类似地, verticalCuts[j] 是从矩形蛋糕的左侧到第 j 个竖直切口的距离。

请你按数组 horizontalCuts 和 verticalCuts 中提供的水平和竖直位置切割后,请你找出 面积最大 的那份蛋糕,并返回其 面积 。由于答案可能是一个很大的数字,因此需要将结果对 10^9 + 7 取余后返回。


示例 1:

输入:h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3]

输出:4 

解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色的那份蛋糕面积最大。

示例 2:

输入:h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1]

输出:6

解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色和黄色的两份蛋糕面积最大。

示例 3:

输入:h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3]

输出:9


提示:

2 <= h, w <= 10^9

1 <= horizontalCuts.length < min(h, 10^5)

1 <= verticalCuts.length < min(w, 10^5)

1 <= horizontalCuts[i] < h

1 <= verticalCuts[i] < w

题目数据保证 horizontalCuts 中的所有元素各不相同

题目数据保证 verticalCuts 中的所有元素各不相同

class Solution {
public:
int f(int len, vector<int>&v)
{
sort(v.begin(),v.end());
int ans=max(v[0],len-v[v.size()-1]);
for(int i=1;i<v.size();i++)ans=max(ans,v[i]-v[i-1]);
return ans;
}
int maxArea(int h, int w, vector<int>& horizontalCuts, vector<int>& verticalCuts) {
long long a=f(h,horizontalCuts),b=f(w,verticalCuts);
return a*b%1000000007;
}
};

1466. 重新规划路线

DFS、BFS ​​javascript:void(0)​

1467. 两个盒子中球的颜色数相同的概率

桌面上有 2n 个颜色不完全相同的球,球上的颜色共有 k 种。给你一个大小为 k 的整数数组 balls ,其中 balls[i] 是颜色为 i 的球的数量。

所有的球都已经 随机打乱顺序 ,前 n 个球放入第一个盒子,后 n 个球放入另一个盒子(请认真阅读示例 2 的解释部分)。

注意:这两个盒子是不同的。例如,两个球颜色分别为 a 和 b,盒子分别为 [] 和 (),那么 [a] (b) 和 [b] (a) 这两种分配方式是不同的(请认真阅读示例 1 的解释部分)。

请计算「两个盒子中球的颜色数相同」的情况的概率。


示例 1:

输入:balls = [1,1]

输出:1.00000

解释:球平均分配的方式只有两种:

- 颜色为 1 的球放入第一个盒子,颜色为 2 的球放入第二个盒子

- 颜色为 2 的球放入第一个盒子,颜色为 1 的球放入第二个盒子

这两种分配,两个盒子中球的颜色数都相同。所以概率为 2/2 = 1 。

示例 2:

输入:balls = [2,1,1]

输出:0.66667

解释:球的列表为 [1, 1, 2, 3]

随机打乱,得到 12 种等概率的不同打乱方案,每种方案概率为 1/12 :

[1,1 / 2,3], [1,1 / 3,2], [1,2 / 1,3], [1,2 / 3,1], [1,3 / 1,2], [1,3 / 2,1], [2,1 / 1,3], [2,1 / 3,1], [2,3 / 1,1], [3,1 / 1,2], [3,1 / 2,1], [3,2 / 1,1]

然后,我们将前两个球放入第一个盒子,后两个球放入第二个盒子。

这 12 种可能的随机打乱方式中的 8 种满足「两个盒子中球的颜色数相同」。

概率 = 8/12 = 0.66667

示例 3:

输入:balls = [1,2,1,2]

输出:0.60000

解释:球的列表为 [1, 2, 2, 3, 4, 4]。要想显示所有 180 种随机打乱方案是很难的,但只检查「两个盒子中球的颜色数相同」的 108 种情况是比较容易的。

概率 = 108 / 180 = 0.6 。

示例 4:

输入:balls = [3,2,1]

输出:0.30000

解释:球的列表为 [1, 1, 1, 2, 2, 3]。要想显示所有 60 种随机打乱方案是很难的,但只检查「两个盒子中球的颜色数相同」的 18 种情况是比较容易的。

概率 = 18 / 60 = 0.3 。

示例 5:

输入:balls = [6,6,6,6,6,6]

输出:0.90327


提示:

1 <= balls.length <= 8

1 <= balls[i] <= 6

sum(balls) 是偶数

答案与真实值误差在 10^-5 以内,则被视为正确答案



思路:

k种颜色的球,从k-1到0依次放球,balls数组存了每种颜色的球的数目。

当放第i个球的时候(i从0到k-1),枚举在左边放球的数量j(j从)

左边还有nl个空位,右边还有nr个空位,那么一共有c[nl+nr][balls[k]]这么多种情况,其中c数组是组合数,

这些情况中,左边放j个,右边放balls[k]-j个的情况数是c[nl][j] * c[nr][balls[k]-j]

所以,这么放的概率是c[nl][j] * c[nr][balls[k]-j] / c[nl+nr][balls[k]]


依次枚举每种颜色的球在左边放的数目,每一种颜色的球的放置都是独立的,不断更新两边的色差,最后色差为0的那些情况的概率加起来,就是最后答案。

代码:

double eps=0.00000001;
class Solution {
public:
double c[51][51];
double ans[25][25][9][20];
vector<int>b;
double getc(int n,int i)
{
if(n<=0||i<=0)return c[n][i]=1;
if(c[n][i]>eps)return c[n][i];
return c[n][i]=getc(n-1,i-1)*n/i;
}
double dp(int nl,int nr,int k,int dif)
{
if(k<0)return (dif==10)?1:0;
if(ans[nl][nr][k][dif]>eps)return ans[nl][nr][k][dif];
int nk=b[k];
double res=0;
for(int i=0;i<=nk;i++)
{
if(nl<i || nr<nk-i)continue;
double p=c[nl][i]*c[nr][nk-i]/c[nl+nr][nk];
int tmpdif=(i?1:0)-((i==nk)?0:1);
res+=p*dp(nl-i,nr-nk+i,k-1,dif-tmpdif);
}
return ans[nl][nr][k][dif]=res;
}
double getProbability(vector<int>& balls) {
b=balls;
for(int i=0;i<50;i++)for(int j=0;j<50;j++)getc(i,j);
int n=0;
for(int i=0;i<b.size();i++)n+=b[i];
return dp(n/2,n/2,b.size()-1,10);
}
};

1470. 重新排列数组

给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,...,xn,y1,y2,...,yn] 的格式排列。

请你将数组按 [x1,y1,x2,y2,...,xn,yn] 格式重新排列,返回重排后的数组。


示例 1:

输入:nums = [2,5,1,3,4,7], n = 3

输出:[2,3,5,4,1,7] 

解释:由于 x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 ,所以答案为 [2,3,5,4,1,7]

示例 2:

输入:nums = [1,2,3,4,4,3,2,1], n = 4

输出:[1,4,2,3,3,2,4,1]

示例 3:

输入:nums = [1,1,2,2], n = 2

输出:[1,2,1,2]


提示:

1 <= n <= 500

nums.length == 2n

1 <= nums[i] <= 10^3

class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
vector<int>ans;
for(int i=0;i<n;i++)
{
ans.push_back(nums[i]);
ans.push_back(nums[n+i]);
}
return ans;
}
};

1471. 数组中的 k 个最强值

给你一个整数数组 arr 和一个整数 k 。

设 m 为数组的中位数,只要满足下述两个前提之一,就可以判定 arr[i] 的值比 arr[j] 的值更强:

 |arr[i] - m| > |arr[j] - m|

 |arr[i] - m| == |arr[j] - m|,且 arr[i] > arr[j]

请返回由数组中最强的 k 个值组成的列表。答案可以以 任意顺序 返回。

中位数 是一个有序整数列表中处于中间位置的值。形式上,如果列表的长度为 n ,那么中位数就是该有序列表(下标从 0 开始)中位于 ((n - 1) / 2) 的元素。

例如 arr = [6, -3, 7, 2, 11],n = 5:数组排序后得到 arr = [-3, 2, 6, 7, 11] ,数组的中间位置为 m = ((5 - 1) / 2) = 2 ,中位数 arr[m] 的值为 6 。

例如 arr = [-7, 22, 17, 3],n = 4:数组排序后得到 arr = [-7, 3, 17, 22] ,数组的中间位置为 m = ((4 - 1) / 2) = 1 ,中位数 arr[m] 的值为 3 。


示例 1:

输入:arr = [1,2,3,4,5], k = 2

输出:[5,1]

解释:中位数为 3,按从强到弱顺序排序后,数组变为 [5,1,4,2,3]。最强的两个元素是 [5, 1]。[1, 5] 也是正确答案。

注意,尽管 |5 - 3| == |1 - 3| ,但是 5 比 1 更强,因为 5 > 1 。

示例 2:

输入:arr = [1,1,3,5,5], k = 2

输出:[5,5]

解释:中位数为 3, 按从强到弱顺序排序后,数组变为 [5,5,1,1,3]。最强的两个元素是 [5, 5]。

示例 3:

输入:arr = [6,7,11,7,6,8], k = 5

输出:[11,8,6,6,7]

解释:中位数为 7, 按从强到弱顺序排序后,数组变为 [11,8,6,6,7,7]。

[11,8,6,6,7] 的任何排列都是正确答案。

示例 4:

输入:arr = [6,-3,7,2,11], k = 3

输出:[-3,11,2]

示例 5:

输入:arr = [-7,22,17,3], k = 2

输出:[22,17]


提示:

1 <= arr.length <= 10^5

-10^5 <= arr[i] <= 10^5

1 <= k <= arr.length

class Solution {
public:
vector<int> getStrongest(vector<int>& arr, int k) {
sort(arr.begin(),arr.end());
int mid=arr[(arr.size()-1)/2];
int low=0,high=arr.size()-1;
vector<int>ans;
while(k--)
{
if(abs(arr[low]-mid)>abs(arr[high]-mid) || abs(arr[low]-mid)==abs(arr[high]-mid) && arr[low]>arr[high])ans.push_back(arr[low++]);
else ans.push_back(arr[high--]);
}
return ans;
}
};

1472. 设计浏览器历史记录

你有一个只支持单个标签页的 浏览器 ,最开始你浏览的网页是 homepage ,你可以访问其他的网站 url ,也可以在浏览历史中后退 steps 步或前进 steps 步。

请你实现 BrowserHistory 类:

BrowserHistory(string homepage) ,用 homepage 初始化浏览器类。

void visit(string url) 从当前页跳访问 url 对应的页面  。执行此操作会把浏览历史前进的记录全部删除。

string back(int steps) 在浏览历史中后退 steps 步。如果你只能在浏览历史中后退至多 x 步且 steps > x ,那么你只后退 x 步。请返回后退 至多 steps 步以后的 url 。

string forward(int steps) 在浏览历史中前进 steps 步。如果你只能在浏览历史中前进至多 x 步且 steps > x ,那么你只前进 x 步。请返回前进 至多 steps步以后的 url 。


示例:

输入:

["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]

[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]

输出:

[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]

解释:

BrowserHistory browserHistory = new BrowserHistory("leetcode.com");

browserHistory.visit("google.com");       // 你原本在浏览 "leetcode.com" 。访问 "google.com"

browserHistory.visit("facebook.com");     // 你原本在浏览 "google.com" 。访问 "facebook.com"

browserHistory.visit("youtube.com");      // 你原本在浏览 "facebook.com" 。访问 "youtube.com"

browserHistory.back(1);                   // 你原本在浏览 "youtube.com" ,后退到 "facebook.com" 并返回 "facebook.com"

browserHistory.back(1);                   // 你原本在浏览 "facebook.com" ,后退到 "google.com" 并返回 "google.com"

browserHistory.forward(1);                // 你原本在浏览 "google.com" ,前进到 "facebook.com" 并返回 "facebook.com"

browserHistory.visit("linkedin.com");     // 你原本在浏览 "facebook.com" 。 访问 "linkedin.com"

browserHistory.forward(2);                // 你原本在浏览 "linkedin.com" ,你无法前进任何步数。

browserHistory.back(2);                   // 你原本在浏览 "linkedin.com" ,后退两步依次先到 "facebook.com" ,然后到 "google.com" ,并返回 "google.com"

browserHistory.back(7);                   // 你原本在浏览 "google.com", 你只能后退一步到 "leetcode.com" ,并返回 "leetcode.com"


提示:

1 <= homepage.length <= 20

1 <= url.length <= 20

1 <= steps <= 100

homepage 和 url 都只包含 '.' 或者小写英文字母。

最多调用 5000 次 visit, back 和 forward 函数。

//id处,覆盖或者添加x
template<typename T>
void finsert(vector<T>&v,int id,T x)
{
if(id<0||id>v.size())return;
if(id==v.size())v.push_back(x);
v[id]=x;
}
class BrowserHistory {
public:
vector<string>v;
int k,len;

BrowserHistory(string homepage) {
v.push_back(homepage),k=0,len=1;
}

void visit(string url) {
finsert(v,++k,url);
len=k+1;
}

string back(int steps) {
return v[k-=min(steps,k)];
}

string forward(int steps) {
return v[k+=min(steps,len-k-1)];
}
};

1473. 给房子涂色 III

DP ​​javascript:void(0)​

1480. 一维数组的动态和

给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。

请返回 nums 的动态和。


示例 1:

输入:nums = [1,2,3,4]

输出:[1,3,6,10]

解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。

示例 2:

输入:nums = [1,1,1,1,1]

输出:[1,2,3,4,5]

解释:动态和计算过程为 [1, 1+1, 1+1+1, 1+1+1+1, 1+1+1+1+1] 。

示例 3:

输入:nums = [3,1,2,10,1]

输出:[3,4,6,16,17]


提示:

1 <= nums.length <= 1000

-10^6 <= nums[i] <= 10^6

class Solution {
public:
vector<int> runningSum(vector<int>& nums) {
for(int i=1;i<nums.size();i++)nums[i]+=nums[i-1];
return nums;
}
};

1481. 不同整数的最少数目

给你一个整数数组 arr 和一个整数 k 。现需要从数组中恰好移除 k 个元素,请找出移除后数组中不同整数的最少数目。


示例 1:

输入:arr = [5,5,4], k = 1

输出:1

解释:移除 1 个 4 ,数组中只剩下 5 一种整数。

示例 2:

输入:arr = [4,3,1,1,3,3,2], k = 3

输出:2

解释:先移除 4、2 ,然后再移除两个 1 中的任意 1 个或者三个 3 中的任意 1 个,最后剩下 1 和 3 两种整数。


提示:

1 <= arr.length <= 10^5

1 <= arr[i] <= 10^9

0 <= k <= arr.length


思路:

先计数,再排序,再贪心,就可以了。

class Solution {
public:
int findLeastNumOfUniqueInts(vector<int>& arr, int k) {
unordered_map<int,int>m;
for(int i=0;i<arr.size();i++)m[arr[i]]++;
vector<int>ans;
for(auto i=m.begin();i!=m.end();i++)ans.push_back(i->second);
sort(ans.begin(),ans.end());
int i;
for(i=0;i<ans.size();i++)
{
if(k>ans[i])k-=ans[i];
else if(k==ans[i])return ans.size()-i-1;
else return ans.size()-i;
}
return 0;
}
};

1482. 制作 m 束花所需的最少天数

二分、三分 ​​javascript:void(0)​


1483. 树节点的第 K 个祖先

给你一棵树,树上有 n 个节点,按从 0 到 n-1 编号。树以父节点数组的形式给出,其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。

请你设计并实现 getKthAncestor(int node, int k) 函数,函数返回节点 node 的第 k 个祖先节点。如果不存在这样的祖先节点,返回 -1 。

树节点的第 k 个祖先节点是从该节点到根节点路径上的第 k 个节点。


示例:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook_07

输入:

["TreeAncestor","getKthAncestor","getKthAncestor","getKthAncestor"]

[[7,[-1,0,0,1,1,2,2]],[3,1],[5,2],[6,3]]

输出:

[null,1,0,-1]

解释:

TreeAncestor treeAncestor = new TreeAncestor(7, [-1, 0, 0, 1, 1, 2, 2]);

treeAncestor.getKthAncestor(3, 1);  // 返回 1 ,它是 3 的父节点

treeAncestor.getKthAncestor(5, 2);  // 返回 0 ,它是 5 的祖父节点

treeAncestor.getKthAncestor(6, 3);  // 返回 -1 因为不存在满足要求的祖先节点


提示:

1 <= k <= n <= 5*10^4

parent[0] == -1 表示编号为 0 的节点是根节点。

对于所有的 0 < i < n ,0 <= parent[i] < n 总成立

0 <= node < n

至多查询 5*10^4 次



这个题有极限用例,5万个结点,单链树(深度为5万),查询次数5万,每次查询的k也是4-5万。

C++暴力是过不了的,但是js暴力能过。


分析:

如果不使用任何数据结构和算法,那么对于单次查询来说,5万次计算是必不可少的,

而对于多次查询来说,既然暴力不能过,那就只有两种情况:

(1)通过数据结构,采用信息换的方式,降低单次查询时间

PS:哈希,字典树,状态压缩等,都属于我理解的信息换,而这题的LCA解法(BFS+DFS+二分)就是属于这一种

(2)通过算法整改解空间,去掉多次查询之间的重复计算

PS:本题的倍增法本质就是动态规划,但是可能动态规划的特征没那么明显


思路一:

用f(node, k)表示答案,在我们求出f(100,50)=x之后,如果再求f(100,51),直接用f(x,1)来求就行了。

可是,如果先求f(100,51),再求(100,50)呢?

如果我们在求f(100,51)的过程中,把f(100,1) ......f(100,51)都存起来,那么这个算法光空间复杂度就n^2了,不行。

继续分析这个大小为n^2的解空间,哪些信息是重复的?

很明显是有的,如果我们存了(100,1) ......f(100,10)=y,也存了f(y,1)......f(y,40),那么f(100,11)......f(51)还有必要存吗?

没有必要,只要跳一次就行了!

假设每个节点只存f(n,1)......f(n,m),那么空间复杂度n*m,时间复杂度n*m+n*n/m,也就是n^1.5,大概等于1千万。

PS:这个思路的前面一半是我比赛时的实际思路,然后我想到带权并查集,然后放弃了这个思路。

PPS:和我 CSU 1515: Sequence ​​javascript:void(0)​​ 这个题的做法也比较像。

赛后代码:

int ans[50005][201];

class TreeAncestor {
public:
vector<int>p;
int n;
TreeAncestor(int n, vector<int>& parent) {
p=parent,this->n=n;
for(int i=0;i<n;i++)ans[i][0]=i;
for(int d=1;d<=200;d++)for(int i=0;i<n;i++)ans[i][d]=ans[i][d-1]>=0?parent[ans[i][d-1]]:-1;
}

int getKthAncestor(int node, int k) {
while(k>200)
{
node=ans[node][200],k-=200;
if(node==-1)return -1;
}
return ans[node][k];
}
};

思路二:倍增法

参考树状数组,用ans[i][j]表示第i个结点的第2^j个祖先

时间复杂度和空间复杂度都是O(nlogn)

比赛代码:

int ans[50005][18];

class TreeAncestor {
public:
vector<int>p;
int n;
TreeAncestor(int n, vector<int>& parent) {
p=parent,this->n=n;
for(int i=0;i<n;i++)ans[i][1]=parent[i];
for(int d=2;d<18;d++)for(int i=0;i<n;i++)ans[i][d]=ans[i][d-1]>=0?ans[ans[i][d-1]][d-1]:-1;
}

int getKthAncestor(int node, int k) {
for(int i=1;i<18;i++)
{
if(k&(1<<(i-1)))node=ans[node][i];
if(node==-1)return -1;
}
return node;
}
};

思路三:

力扣题解中的BFS+DFS+二分


PS:

LCA最近公共祖先问题,可以用BFS+DFS+RMQ区间极值来做,也可以用倍增法来做。

这个题的倍增法,和LCA的倍增法差不多。

这个题的BFS+DFS+二分解法,和LCA的BFS+DFS+RMQ解法差不多。

1486. 数组异或操作

给你两个整数,​​n​​​ 和 ​​start​​ 。

数组 ​​nums​​​ 定义为:​​nums[i] = start + 2*i​​​(下标从 0 开始)且 ​​n == nums.length​​ 。

请返回 ​​nums​​ 中所有元素按位异或(XOR)后得到的结果。


示例 1:

输入:n = 5, start = 0

输出:8

解释:数组 nums 为 [0, 2, 4, 6, 8],其中 (0 ^ 2 ^ 4 ^ 6 ^ 8) = 8 。

     "^" 为按位异或 XOR 运算符。

示例 2:

输入:n = 4, start = 3

输出:8

解释:数组 nums 为 [3, 5, 7, 9],其中 (3 ^ 5 ^ 7 ^ 9) = 8.

示例 3:

输入:n = 1, start = 7

输出:7

示例 4:

输入:n = 10, start = 5

输出:2


提示:


  • ​1 <= n <= 1000​
  • ​0 <= start <= 1000​
  • ​n == nums.length​

class Solution {
public:
int xorOperation(int n, int start) {
if(n==1)return start;
return start^xorOperation(n-1,start+2);
}
};

1487. 保证文件名唯一

给你一个长度为 ​​n​​​ 的字符串数组 ​​names​​​ 。你将会在文件系统中创建 ​​n​​​ 个文件夹:在第 ​​i​​​ 分钟,新建名为 ​​names[i]​​ 的文件夹。

由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 ​​(k)​​​ 的形式为新文件夹的文件名添加后缀,其中 ​​k​​ 是能保证文件名唯一的 最小正整数 。

返回长度为 n​​ 的字符串数组,其中 ​​ans[i]​​​ 是创建第 ​​i​​ 个文件夹时系统分配给该文件夹的实际名称。


示例 1:

输入:names = ["pes","fifa","gta","pes(2019)"]
输出:["pes","fifa","gta","pes(2019)"]
解释:文件系统将会这样创建文件名:
"pes" --> 之前未分配,仍为 "pes"
"fifa" --> 之前未分配,仍为 "fifa"
"gta" --> 之前未分配,仍为 "gta"
"pes(2019)" --> 之前未分配,仍为 "pes(2019)"

示例 2:

输入:names = ["gta","gta(1)","gta","avalon"]
输出:["gta","gta(1)","gta(2)","avalon"]
解释:文件系统将会这样创建文件名:
"gta" --> 之前未分配,仍为 "gta"
"gta(1)" --> 之前未分配,仍为 "gta(1)"
"gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)" 。
"avalon" --> 之前未分配,仍为 "avalon"

示例 3:

输入:names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"]
输出:["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"]
解释:当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。

示例 4:

输入:names = ["wano","wano","wano","wano"]
输出:["wano","wano(1)","wano(2)","wano(3)"]
解释:每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。

示例 5:

输入:names = ["kaido","kaido(1)","kaido","kaido(1)"]
输出:["kaido","kaido(1)","kaido(2)","kaido(1)(1)"]
解释:注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。


提示:


  • ​1 <= names.length <= 5 * 10^4​
  • ​1 <= names[i].length <= 20​
  • ​names[i]​​ 由小写英文字母、数字和/或圆括号组成。

class Solution {
public:
vector<string> getFolderNames(vector<string>& names) {
unordered_map<string,int>m;
m.reserve(50005);
vector<string>ans;
char cn[6];
for(int i=0;i<names.size();i++)
{
string s=names[i],s1;
if(m[s]==0)
{
ans.push_back(s);
m[s]=1;
continue;
}
for(int i=m[s];i<50005;i++)
{
sprintf(cn,"%d",i);
s1=s+"("+cn+")";
if(m[s1]==0)
{
ans.push_back(s1);
m[s]=i+1;
m[s1]=1;
break;
}
}
}
return ans;
}
};


PS:

我WA的用例:

["kaido","kaido(1)","kaido","kaido(1)","kaido(2)"]

["kaido","kaido(1)","kaido(2)","kaido(1)(1)","kaido(2)(1)"]

修改代码:

s和s1都要存入map,改完AC

1488. 避免洪水泛滥

线段树 ​​javascript:void(0)​

1496. 判断路径是否相交

给你一个字符串 path,其中 path[i] 的值可以是 'N'、'S'、'E' 或者 'W',分别表示向北、向南、向东、向西移动一个单位。

机器人从二维平面上的原点 (0, 0) 处开始出发,按 path 所指示的路径行走。

如果路径在任何位置上出现相交的情况,也就是走到之前已经走过的位置,请返回 True ;否则,返回 False 。


示例 1:

输入:path = "NES"

输出:false 

解释:该路径没有在任何位置相交。

示例 2:

输入:path = "NESWW"

输出:true

解释:该路径经过原点两次。


提示:

1 <= path.length <= 10^4

path 仅由 {'N', 'S', 'E', 'W} 中的字符组成


C++代码:

class Solution {
public:
bool isPathCrossing(string path) {
int ns=0,ew=0;
map<pair<int,int>,int>m;
m[make_pair(ns,ew)]=1;
for(int i=0;path[i];i++)
{
if(path[i]=='N')ns++;
if(path[i]=='S')ns--;
if(path[i]=='E')ew++;
if(path[i]=='W')ew--;
if(m[make_pair(ns,ew)])return true;
m[make_pair(ns,ew)]=1;
}
return false;
}
};

C代码:

typedef struct {
int key;
int value;
UT_hash_handle hh;
} Hash;
Hash *hash = NULL;

// 增加
void add(int key, int value)
{
Hash *s = NULL;
s = (Hash *)malloc(sizeof(Hash));
s->key = key;
s->value = value;
HASH_ADD_INT(hash, key, s);
}
// 查找
int find(int key)
{
Hash *s = NULL;
HASH_FIND_INT(hash, &key, s);
if (s != NULL) {
// 查找到结果
return 1;
} else {
return 0;
}
}
// 删除
void delete (Hash *s)
{
HASH_DEL(hash, s);
free(s);
s = NULL;
}
//清空
void clearAll()
{
Hash *s,*tmp;
HASH_ITER(hh, hash, s, tmp){
delete(s);
}
}

bool isPathCrossing(char * path){
int ns=0,ew=0;
add(0,1);
for(int i=0;path[i];i++)
{
if(path[i]=='N')ns++;
if(path[i]=='S')ns--;
if(path[i]=='E')ew++;
if(path[i]=='W')ew--;
if(find(ns*10000+ew))
{
clearAll();
return true;
}
add(ns*10000+ew,1);
}
clearAll();
return false;
}


1502. 判断能否形成等差数列

给你一个数字数组 arr 。

如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。

如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。


示例 1:

输入:arr = [3,5,1]

输出:true

解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。

示例 2:

输入:arr = [1,2,4]

输出:false

解释:无法通过重新排序得到等差数列。


提示:

2 <= arr.length <= 1000

-10^6 <= arr[i] <= 10^6

class Solution {
public:
bool canMakeArithmeticProgression(vector<int>& arr) {
sort(arr.begin(), arr.end());
int k = arr[1] - arr[0];
for(int i = 2; i < arr.size(); i++)if (arr[i] - arr[i - 1] - k)return false;
return true;
}
};


1503. 所有蚂蚁掉下来前的最后一刻

有一块木板,长度为 n 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以 每秒一个单位 的速度移动。其中,一部分蚂蚁向 左 移动,其他蚂蚁向 右 移动。

当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。

而当蚂蚁在某一时刻 t 到达木板的一端时,它立即从木板上掉下来。

给你一个整数 n 和两个整数数组 left 以及 right 。两个数组分别标识向左或者向右移动的蚂蚁在 t = 0 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。


示例 1:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_i++_08

输入:n = 4, left = [4,3], right = [0,1]

输出:4

解释:如上图所示:

-下标 0 处的蚂蚁命名为 A 并向右移动。

-下标 1 处的蚂蚁命名为 B 并向右移动。

-下标 3 处的蚂蚁命名为 C 并向左移动。

-下标 4 处的蚂蚁命名为 D 并向左移动。

请注意,蚂蚁在木板上的最后时刻是 t = 4 秒,之后蚂蚁立即从木板上掉下来。(也就是说在 t = 4.0000000001 时,木板上没有蚂蚁)。

示例 2:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_facebook_09

输入:n = 7, left = [], right = [0,1,2,3,4,5,6,7]

输出:7

解释:所有蚂蚁都向右移动,下标为 0 的蚂蚁需要 7 秒才能从木板上掉落。

示例 3:

力扣OJ(1400+)1414. 和为 K 的最少斐波那契数字数目_i++_10

输入:n = 7, left = [0,1,2,3,4,5,6,7], right = []

输出:7

解释:所有蚂蚁都向左移动,下标为 7 的蚂蚁需要 7 秒才能从木板上掉落。

示例 4:

输入:n = 9, left = [5], right = [4]

输出:5

解释:t = 1 秒时,两只蚂蚁将回到初始位置,但移动方向与之前相反。

示例 5:

输入:n = 6, left = [6], right = [0]

输出:6


提示:

1 <= n <= 10^4

0 <= left.length <= n + 1

0 <= left[i] <= n

0 <= right.length <= n + 1

0 <= right[i] <= n

1 <= left.length + right.length <= n + 1

left 和 right 中的所有值都是唯一的,并且每个值 只能出现在二者之一 中。

class Solution {
public:
int getLastMoment(int n, vector<int>& left, vector<int>& right) {
int ans = 0;
for (int i = 0; i < left.size(); i++)ans = max(ans, left[i]);
for (int i = 0; i < right.size(); i++)ans = max(ans, n-right[i]);
return ans;
}
};


1504. 统计全 1 子矩形

DP ​​javascript:void(0)​

1512. 好数对的数目

给你一个整数数组 ​​nums​​ 。

如果一组数字 ​​(i,j)​​​ 满足 ​​nums[i]​​​ == ​​nums[j]​​​ 且 ​​i​​​ < ​​j​​ ,就可以认为这是一组 好数对 。

返回好数对的数目。


示例 1:

输入:nums = [1,2,3,1,1,3]
输出:4
解释:有 4 组好数对,分别是 (0,3), (0,4), (3,4), (2,5) ,下标从 0 开始

示例 2:

输入:nums = [1,1,1,1]
输出:6
解释:数组中的每组数字都是好数对

示例 3:

输入:nums = [1,2,3]
输出:0


提示:


  • ​1 <= nums.length <= 100​
  • ​1 <= nums[i] <= 100​

class Solution {
public:
int numIdenticalPairs(vector<int>& nums) {
int ans=0;
for(int i=0;i<nums.size();i++)for(int j=i+1;j<nums.size();j++)ans+=(nums[i]==nums[j]);
return ans;
}
};

1513. 仅含 1 的子串数

给你一个二进制字符串 ​​s​​(仅由 '0' 和 '1' 组成的字符串)。

返回所有字符都为 1 的子字符串的数目。

由于答案可能很大,请你将它对 10^9 + 7 取模后返回。


示例 1:

输入:s = "0110111"
输出:9
解释:共有 9 个子字符串仅由 '1' 组成
"1" -> 5 次
"11" -> 3 次
"111" -> 1 次

示例 2:

输入:s = "101"
输出:2
解释:子字符串 "1" 在 s 中共出现 2 次

示例 3:

输入:s = "111111"
输出:21
解释:每个子字符串都仅由 '1' 组成

示例 4:

输入:s = "000"
输出:0


提示:


  • ​s[i] == '0'​​​ 或 ​​s[i] == '1'​
  • ​1 <= s.length <= 10^5​

class Solution {
public:
int numSub(string s) {
int len=0,ans=0;
for(int i=0;i<s.length();i++)
{
if(s[i]-'0')len++;
else len=0;
ans+=len,ans%=1000000007;
}
return ans;
}
};