1520、给你一个数字数组 arr 。
如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。
如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。
示例 1:
输入:arr = [3,5,1]
输出:true
解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。
class Solution {
public:
bool canMakeArithmeticProgression(vector<int>& arr) {
sort(arr.begin(),arr.end());
int t=arr[1]-arr[0];
for(int i=2;i<arr.size();i++){
if(arr[i]-arr[i-1]!=t) return false;
}
return true;
}
};
结果:
执行用时:8 ms, 在所有 C++ 提交中击败了85.23% 的用户
内存消耗:9 MB, 在所有 C++ 提交中击败了100.00%
1503、有一块木板,长度为 n 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以 每秒一个单位 的速度移动。其中,一部分蚂蚁向 左 移动,其他蚂蚁向 右 移动。
当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。
而当蚂蚁在某一时刻 t 到达木板的一端时,它立即从木板上掉下来。
给你一个整数 n 和两个整数数组 left 以及 right 。两个数组分别标识向左或者向右移动的蚂蚁在 t = 0 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。
示例 1:
输入:n = 4, left = [4,3], right = [0,1]
输出:4
解释:如上图所示:
-下标 0 处的蚂蚁命名为 A 并向右移动。
-下标 1 处的蚂蚁命名为 B 并向右移动。
-下标 3 处的蚂蚁命名为 C 并向左移动。
-下标 4 处的蚂蚁命名为 D 并向左移动。
请注意,蚂蚁在木板上的最后时刻是 t = 4 秒,之后蚂蚁立即从木板上掉下来。(也就是说在 t = 4.0000000001 时,木板上没有蚂蚁)。
时间每次递增0.5,对每个蚂蚁下一步到达的位置进行判断,若该位置有蚂蚁,则二者均掉头。
132 / 167 个通过测试用例,结果导致超时。证明思路可行,但是不优。
class Solution {
public:
int getLastMoment(int n, vector<int>& left, vector<int>& right) {
// cout<<"---------------------"<<endl;
vector<pair<float,int>> vec;//保存每个虫子的位置、方向
unordered_map<float,int> pos;//存每个地点,虫子标号
for(auto i:left) vec.push_back({i,-1});
for(auto j:right) vec.push_back({j,1});
vector<bool> mark(vec.size(),false);//记录虫子是否掉下
int num=vec.size();//虫子总数
for(float t=0.5;;t+=0.5){
pos.clear();
for(int i=0;i<vec.size();i++){
if(mark[i]) continue;
vec[i].first=vec[i].first+0.5*vec[i].second;
if(vec[i].first<=0||vec[i].first>=n) {
mark[i]=true;
num--;
}
if(num==0) return int(t);
if(!pos.empty() && pos.count(vec[i].first)){
vec[i].second*=-1;
vec[pos[vec[i].first]].second*=-1;
}else{
pos.insert({vec[i].first,i});
}
}
}
return 0;
}
};
修改:
该题目类似益智类,对题目进行分析,不急于立刻模拟蚂蚁的运动。
分析后,每只蚂蚁相遇调换位置--->二者灵魂互换,依旧改往左往左,往右往右。
原本名字为a的蚂蚁1往左,名字为b的蚂蚁2往右--->相遇--->名字为a的蚂蚁1向右,名字为b的蚂蚁2向左。
--->将名字与肉体分开,向左的名字一直是a,向右的一直是名字为b,只是不是同一只蚂蚁。
--->名字为a的蚂蚁2向左,名字为b的蚂蚁1向右。
vector数组中,求最值。 max_element(v.begin(),v.end());返回的是迭代器
*max_element(vec.begin(),vec.end());
*min_element(vec.begin(),vec.end());
// 忽视相遇掉头,直接看成向左向右的蚂蚁,走在两条独立的道路
class Solution {
public:
int getLastMoment(int n, vector<int>& left, vector<int>& right) {
//向左max
int l=-1,r=-1;
if(left.size()) l=*max_element(left.begin(),left.end());
//向右max
if(right.size()) r=n-*min_element(right.begin(),right.end());
return max(l,r);
}
};
结果:
执行用时:64 ms, 在所有 C++ 提交中击败了52.32% 的用户
内存消耗:22.8 MB, 在所有 C++ 提交中击败了100.00% 的用户
1504、给你一个只包含 0 和 1 的 rows * columns 矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。
示例 1:
输入:mat = [[1,0,1],
[1,1,0],
[1,1,0]]
输出:13
解释:
有 6 个 1x1 的矩形。
有 2 个 1x2 的矩形。
有 3 个 2x1 的矩形。
有 1 个 2x2 的矩形。
有 1 个 3x1 的矩形。
矩形数目总共 = 6 + 2 + 3 + 1 + 1 = 13 。
暴力,导致超时。32 / 72 个通过测试用例。
// 对每个可能的子矩阵进行遍历。暴力
int check(int i,int j,int n,int m,vector<vector<int>> v){
for(int a=0;a<n;a++)
for(int b=0;b<m;b++)
if(v[i+a][j+b]==0) return 0;
return 1;
}
class Solution {
public:
int numSubmat(vector<vector<int>>& mat) {
int ans=0;
int row=mat.size(),col=mat[0].size();
for(int n=1;n<=row;n++)
for(int m=1;m<=col;m++){
for(int i=0;i<row;i++)
for(int j=0;j<col;j++)
if(mat[i][j] && i+n-1<row && j+m-1<col) ans+=check(i,j,n,m,mat);
}
return ans;
}
};
修改:
mat里每个点(i.j)统计该行,其左边最多有几个连续的1,存为left[i][j]。然后对于每个点(i.j),固定为子矩形的右下角为(i.j),利用left从该行i向上寻找子矩阵左上角为第k行的矩阵个数。每次将子矩阵个数加入ans。
刚方法,减少了一层for循环,该部分的复杂度用dp实现为O(n+n^3),相比上一个O(n^4)增速不少。
class Solution {
public:
int numSubmat(vector<vector<int>>& mat) {
int row=mat.size(),col=mat[0].size();
int dp[row][col];//每个元素,所在行,元素之前(包含该元素)最多的连续1长度
for(int i=0;i<row;i++){
dp[i][0]=mat[i][0];
for(int j=1;j<col;j++){
if(mat[i][j])dp[i][j]=dp[i][j-1]+1;
else dp[i][j]=0;
}
}
// 每次以(i,j)为矩形右下角,计算矩形个数
int ans=0;
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
if(!mat[i][j]) continue;
int minlen=1E9+7;
for(int k=i;k>=0;k--){ //从该元素(i,j)同列j,向上遍历(i-1,j),(i-2,j)...,直到遇见dp==0。在i行构成矩形为1*n,在i-1构成矩形为2*n,在i-2行构成矩形为3*n
if(dp[k][j]==0) break;;
minlen=min(minlen,dp[k][j]); //在向上遍历时,可能1排列成沙漏状态,两头大,中间小。minlen用于记录该沙漏状态的最小宽度。
ans+=minlen;
}
}
}
return ans;
}
};
结果:
执行用时:64 ms, 在所有 C++ 提交中击败了96.06% 的用户
内存消耗:13.5 MB, 在所有 C++ 提交中击败了100.00% 的用户
树状数组
1505、给你一个字符串 num 和一个整数 k 。其中,num 表示一个很大的整数,字符串中的每个字符依次对应整数上的各个 数位 。
你可以交换这个整数相邻数位的数字 最多 k 次。
请你返回你能得到的最小整数,并以字符串形式返回。
示例 1:
输入:num = "4321", k = 4
输出:"1342"
解释:4321 通过 4 次交换相邻数位得到最小整数的步骤如上图所示。示例 2:
输入:num = "100", k = 1
输出:"010"
解释:输出可以包含前导 0 ,但输入保证不会有前导 0 。
树状数组:
树状数组能够实现O(logn)
的时间求前缀和,O(logn)
的时间来修改某个数。
1、前缀和
2、树状数组的构造
3、lowbit()
lowbit(x) = x & (-x);
这里 x
一定是正整数,即 x >= 1
; &
表示按位与运算;
4、标准结构:
int lowbit(int x) {
return x & -x;
}
// 求1 - x的所有元素和, 求前缀和
int get(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(x)) res += tr[i];
return res;
}
// 在x位置处加c,单点更新 && 可用于初始化tr[]树状数组
void add(int x, int c) {
for (int i = x; i <= n; i += lowbit(x)) tr[i] += c;
}
定义一个新的数组tr[N]
,tr[i]
存储的是,i
前面,lowbit(i)
个元素的和。每个节点的父节点为i + lowbit(i)
- 求前缀和:计算
1 - a
范围内的元素和的时候,只需要迭代求解即可。 - 单点更新:只需要找到当前节点的直接父节点,然后让其更新,在找到这个接待的父节点,直到找到最顶层的节点。
初始化树状数组的方法:
逐个扫描元素,并调用add方法,时间复杂度为O(nlogn)O(nlogn),初始化tr[]。
该题目解法:
// 贪心算法 + 树状数组
// 贪心:对高位尽可能安排小的数字,然后是次高位尽可能小。为每一个数字,存储其在string中的下标。
// 对每个数位进行遍历,判断0-9s依次是否有空余,可以被填入当前数位。
// 1)若存在,则获取该数字在原string中的下标,由树状数组得该数字换到当前数位需要的次数(二者坐标差)。判断该次数能否满足。
// 2)若不存在,判断下一个数字。
// 树状数组:维护在交换过程中,其他数字的位置变化??
vector<int> tr; //树状数组,存储每一个下标在交换过程中,移动的和
vector<int> pos[11];// 0-9每个数字,在string中出现的下标(每个数字出现的次数可以>=1)
int cur[11];//0-9每个数字已经被用了几个,还有多少余下可以继续被用
int n;//数位个数
//树状数组的结构:
int lowbit(int x){
return x & (-x);
}
void add(int x,int c){
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int get(int x){
int ans=0;
for(int i=x;i>0;i-=lowbit(i)) ans+=tr[i];
return ans;
}
class Solution {
public:
string minInteger(string num, int k) {
n = num.size();
tr.clear();
for(int i=0;i<=n;i++) tr.push_back(0);
for(int i=0;i<10;i++) pos[i].clear(),cur[i]=0;
for(int i=0;i<n;i++) pos[num[i]-'0'].push_back(i+1); //因为树状数组从1开始
string ans="";
for(int i=0;i<n;i++){
for(int j=0;j<10;j++){
if(cur[j]>=pos[j].size()) continue;//j数字,已经被用完
int op=pos[j][cur[j]]; //该j数组=字,在string中最初始的位置
int cost=op+get(op)-1;
if(cost<=k){
k-=cost;
cur[j]++;
add(op,-1);
ans+=char('0'+j);
break;
}
}
}
return ans;
}
};
结果:
执行用时:76 ms, 在所有 C++ 提交中击败了73.74% 的用户
内存消耗:9.4 MB, 在所有 C++ 提交中击败了100.00% 的用户