原题链接
一个 n x n 的整数矩阵 grid 中,每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。
当开始下雨时,在时间为 t 时,水池中的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。
你从坐标方格的左上平台 (0,0) 出发。返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。
示例 1:
输入: grid = [[0,2],[1,3]]
输出: 3
解释:
时间为0时,你位于坐标方格的位置为 (0, 0)。
此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。
等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置
示例 2:
输入: grid = [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
输出: 16
解释:
我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的
这道题说了这么多其实就是让我们找到一个最小的数字使得从他在平台中的位置出发可以联通 ( 0 , 0 ) (0,0) (0,0)和 ( n − 1 , n − 1 ) (n-1,n-1) (n−1,n−1),要找到这个数字我们可以二分出一个答案然后对他进行合法性判断即可.
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//查找的方向
bool vis[55][55];//判断grid的某个坐标是否被查找过避免重复
class Solution {
public:
queue<pair<int,int>> q;//记录坐标
bool check(int time,vector<vector<int>>& grid,int row,int col){
int i;
int cur,x,y,tx,ty;
memset(vis,false,sizeof(vis));//每次模拟都要初始化vis数组
if(grid[0][0]<=time){
q.push(make_pair(0,0));
vis[0][0]=true;
}else {
return false;//如果当前模拟的time比(0,0)处的时间都要小肯定不合法
}
while(!q.empty()){
pair<int,int> tmp=q.front();
q.pop();
x=tmp.first;
y=tmp.second;
for(i=0;i<4;i++){
tx=x+dir[i][0];
ty=y+dir[i][1];
if(tx>=0&&ty>=0&&tx<row&&ty<col){
if(!vis[tx][ty]&&grid[tx][ty]<=time){
q.push(make_pair(tx,ty));
vis[tx][ty]=true;
}
}//方向合法并且没有被遍历过且时间小于等于当前模拟的时间就入队并且标记
}
}
return vis[row-1][col-1];//如果能够连通就是true否则是false
}
int swimInWater(vector<vector<int>>& grid) {
int l=0,r=2500;
int mid,ans=0;
int row=grid.size(),col=grid[0].size();
while(l<=r){
mid=l+((r-l)>>1);
if(check(mid,grid,row,col)){
ans=mid;
r=mid-1;
}else l=mid+1;
}
return ans;
}
};
原题链接
给你两个长度相同的字符串,s 和 t。
将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。
用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。
如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。
如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。
示例 1:
输入:s = “abcd”, t = “bcdf”, maxCost = 3
输出:3
解释:s 中的 “abc” 可以变为 “bcd”。开销为 3,所以最大长度为 3。
示例 2:
输入:s = “abcd”, t = “cdef”, maxCost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。
示例 3:
输入:s = “abcd”, t = “acde”, maxCost = 0
输出:1
解释:a -> a, cost = 0,字符串未发生变化,所以最大长度为 1。
提示:
1 <= s.length, t.length <= 10^5
0 <= maxCost <= 10^6
s 和 t 都只含小写英文字母。
1 ) 1) 1) 我们首先考虑将 s [ i ] s[i] s[i]转换成 t [ i ] t[i] t[i]所需要的花费即是 a b s ( s [ i ] − t [ i ] ) abs(s[i]-t[i]) abs(s[i]−t[i]),接着把他们放入到一个前缀和数组中去。
2 ) 2) 2)为什么要构建前缀和数组呢?这是因为我们既然要求转化花费小于 m a x c o s t maxcost maxcost的条件下长度的最大值很自然会想到二分算法,那么怎么求利用二分就成了问题。
3 ) 3) 3)考虑某个范围 [ i , j ] [i,j] [i,j]当我们目前枚举到了 j j j位置时候,其转化的花费就是 p r e v [ j ] − p r e v [ i − 1 ] prev[j]-prev[i-1] prev[j]−prev[i−1],所以在 j j j固定的情况下我们向他之前的元素看去,我们可以利用二分找到了在花费小于 m a x c o s t maxcost maxcost的最小 i i i,然后用 j − i j-i j−i与之前判断的最大长度进行判断即可。
4 ) 4) 4)现在问题来到了如何二分进行查找,边界条件如何选择。由于我们可能枚举到 0 0 0这个位置,所以左边界定义为-1,右边界自然就是 j − 1 j-1 j−1了,为什么这样处理?因为这样的话我们二分到的位置 m i d mid mid正好是原数组对应的 m i d + 1 mid+1 mid+1位置的元素,正好符合我们的公式,并且二者之间的长度也是 j − m i d j-mid j−mid。
例如现在我们现在处于 j 位 置 , j位置, j位置,二分枚举到了 m i d mid mid这个位置,并且通过 p r e v [ j ] − p r e v [ m i d ] prev[j]-prev[mid] prev[j]−prev[mid]计算出了花费小于 m a x c o s t maxcost maxcost,那么就把 n u m num num改为 m i d mid mid,当我们跳出二分查找之后与之前的长度比较选择较大的即可。
class Solution {
public:
int prev[100005];
int getcnt(int i){
if(i==-1){
return 0;
}return prev[i];
}
int equalSubstring(string s, string t, int maxCost) {
int n=s.size();
for(int i=0;i<n;i++){
prev[i]=i?abs(s[i]-t[i])+prev[i-1]:abs(s[i]-t[i]);
}
int ans=0;
for(int i=0;i<n;i++){
int l=-1,r=i-1;//这样正好相等于把枚举到的区间坐标整体前移不用考虑计算前缀和时候的-1和计算长度时候的+1
int tmp=i;//tmp用来记录mid,这里要初始化成i以免找不到mid的时候也记录上了数量
while(l<=r){
int mid=((r-l)>>1)+l;
if((getcnt(i)-getcnt(mid))<=maxCost){
tmp=mid;
r=mid-1;
}else l=mid+1;
}
ans=max(ans,i-tmp);
}
return ans;
}
};
原题链接
给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与 w[i] 成正比。
例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。
也就是说,选取下标 i 的概率为 w[i] / sum(w) 。
示例 1:
输入:
inputs = [“Solution”,“pickIndex”]
inputs = [[[1]],[]]
输出:
[null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
示例 2:
输入:
inputs = [“Solution”,“pickIndex”,“pickIndex”,“pickIndex”,“pickIndex”,“pickIndex”]
inputs = [[[1,3]],[],[],[],[],[]]
输出:
[null,1,1,1,1,0]
解释:
Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。
由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
…
诸若此类。
首先这道题要求我们按照权重来返回一个随机下标,这就是说某个下标返回的概率就是他的权重除以总权,例如: n u m s = 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 nums=1,1,1,1,1,0,0,0,0,0 nums=1,1,1,1,1,0,0,0,0,0,那么权为1的返回概率就是100%,0也是0%。
以为题目中有这总权这个概念,于是我们决定利用前缀和+二分查找来实现。
思路就是定义一个前缀和数组,在返回坐标的时候我们先得到一个在数组数值范围内的数利用 r a n g e = range= range= r a n d ( ) rand() rand()% n u m . b a c k ( ) num.back() num.back()即可得到,接着把二分区间定义为 [ 0 , n u m . s i z e ( ) − 1 ] [0,num.size()-1] [0,num.size()−1],之后再找到第一个大于的下标即可。
class Solution {
public:
vector<int> ans;
Solution(vector<int>& w) {
int sum=0;
for(int i=0;i<w.size();i++){
sum+=w[i];
ans.push_back(sum);//前缀和数组
}
}
int pickIndex() {
int rang=rand()%ans.back();//range随机得到一个在数组数值范围内的数
int l=0,r=ans.size()-1;//
int ret=0;
while(l<=r){
int mid=((r-l)>>1)+l;
if(ans[mid]>rang){
ret=mid;
r=mid-1;
}else l=mid+1;
}
return ret;
}
};
原题链接
给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
依然是前缀和+二分,先计算出从0到某个坐标的0的个数,接着我们来看一个可行解的条件:
我们现在枚举到了下标为 j j j的点,现在我们希望找到一个最小的满足从他到 j j j的0<=k的坐标 i i i,即是:
p r e v [ j ] − p r e v [ i − 1 ] < = k prev[j]-prev[i-1]<=k prev[j]−prev[i−1]<=k
我们按照这个公式找到一个最小的 i i i求出其长度跟之前的长度进行比较即可.边界的处理跟之前的某个题类似甚至说这两个题完全是同一个题.
class Solution {
public:
int prev[100005];
int getcnt(int i){
return i==-1?0:prev[i];
}
int longestOnes(vector<int>& nums, int k) {
int ans=0;
for(int i=0;i<nums.size();i++){
prev[i]=i==0?!nums[i]:prev[i-1]+!nums[i];
}
for(int i=0;i<nums.size();i++){
int l=-1,r=i-1;
int tmp=i;
while(l<=r){
int mid=((r-l)>>1)+l;
if(getcnt(i)-getcnt(mid)<=k)
{
tmp=mid;
r=mid-1;
}else l=mid+1;
}
ans=max(ans,i-tmp);
}
return ans;
}
};
不过这种方法只是练习前缀和加二分,并不是最优解。
原题链接
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例 1:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3
示例 2:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。
1 ) 1) 1)首先注意这道题跟一般的二分不同,因为我们印象中使用二分的条件前提就是数组得有序。但是这个题目一反常态,他要求我们只能采摘连续的花朵,也就是说我们不能对数组进行排序。
2 ) 2) 2)我们可以这样考虑,首先如果 b l o o m D a y . s i z e ( ) < m ∗ k bloomDay.size()
3 ) 3) 3)于是二分区间就有了: [ m i n , m a x ] [min,max] [min,max],虽然开花时间不是递增的,但是开花的数目却是递增的,也就是说我们可以对于一个 m i d mid mid去统计 b l o o m D a y bloomDay bloomDay中连续开花的数目,我们将其定义为 b l o o m bloom bloom,如果 b l o o m D a y [ i ] < = m i d bloomDay[i]<=mid bloomDay[i]<=mid, b l o o m + + bloom++ bloom++,否则将其赋值为0.另外定义一个当前做好的花束个数 n u m num num,如果 b l o o m = = k bloom==k bloom==k的话 n u m + + num++ num++最后二分结束开始判断。其实仔细分析的话这道题并没有什么难度。
class Solution {
public:
int minDays(vector<int>& bloomDay, int m, int k) {
if(m*k>bloomDay.size()){
return -1;
}
int ans=0;
int minday=*min_element(bloomDay.begin(),bloomDay.end());
//这个的话就是寻找一个容器中最小元素的迭代器通过解引用就可以得到最小值了下列的max_element同理
int maxday=*max_element(bloomDay.begin(),bloomDay.end());
int l=minday,r=maxday;
while(l<=r){
int mid=((r-l)>>1)+l;
int bloom=0,num=0;
for(int i=0;i<bloomDay.size();i++){
if(bloomDay[i]<=mid){
bloom++;
} else bloom=0;
if(bloom==k){
num++;
bloom=0;
}
}
if(num>=m){
r=mid-1;//去寻找下一可能更小的mid
ans=mid;
}else l=mid+1;.//当前的mid不能满足条件寻找更大的
}
return ans;
}
};
原题链接
给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数,我们就认为这个子数组是「优美子数组」。
请返回这个数组中 「优美子数组」 的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length
1 ) 1) 1)求解连续子数组,很容易想到前缀和,我们首先定义一个 p r e v [ i ] prev[i] prev[i]数组,他表示从 0 到 i 0到i 0到i有多少个奇数元素,接着开始寻找答案:
2 ) 2) 2)首先考虑合法答案是什么样的,首先对于 n u m s nums nums数组,下标从 n n n 到 m m m.他是合法答案的条件是 p r e v [ m ] − p r e v [ n − 1 ] = = k prev[m]-prev[n-1]==k prev[m]−prev[n−1]==k,再转化一下, p r e v [ m ] − k = p r e v [ n − 1 ] prev[m]-k=prev[n-1] prev[m]−k=prev[n−1],加入我们目前遍历到的元素是 n u m s [ m ] nums[m] nums[m]那么我们去寻找符合条件的最小和最大的 n n n两者之间的长度就是以 n u m s [ m ] nums[m] nums[m]为尾的连续子数组的合法答案.
class Solution {
public:
int prev[100005];
int findmax(int l,int r,int target){
int mid,ans=-1;
while(l<=r){
mid=((r-l)>>1)+l;
if(target>=prev[mid-1]){
l=mid+1;
ans=mid;//这是在寻找最后一个大于等于target的元素
}else r=mid-1;
}
if(ans!=-1&&prev[ans-1]!=target)
return -1;//如果我们找到了一个可行解,但是可行解的前缀和并不是target说明他仍是错误答案返回-1
return ans;
}
int findmin(int l,int r,int target){
int mid,ans=-1;
while(l<=r){
mid=((r-l)>>1)+l;
if(target<=prev[mid-1]){
ans=mid;
r=mid-1;//这是在寻找第一个小于等于target的元素
}else l=mid+1;
}
if(ans!=-1&&prev[ans-1]!=target){
return -1;
}
return ans;
}
int numberOfSubarrays(vector<int>& nums, int k) {
int ans=0;
memset(prev,0,sizeof(prev));
for(int i=0;i<nums.size();i++){
prev[i]=i==0?nums[i]&1:(nums[i]&1)+prev[i-1];//前缀和
}
int l=-1,r=-1;
for(int i=0;i<nums.size();i++){
if(prev[i]==k){
ans++;//以0为首以当前元素结尾的情况很好分析这里直接判断
}
l=findmin(1,i,prev[i]-k);//从1开始找是因为0的情况之前已经找过了
r=findmax(1,i,prev[i]-k);
if(l!=-1){
ans+=r-l+1;//如果找到了合法的l一定有合法的r但是有合法的r不一定有合法的l因为prev数组单调不降
}
}
return ans;
}
};