【leetcode】周赛196---(1)1502. 能否等差 (2) 1503. 所有蚂蚁掉下来(3)1504. 统计全 1 子矩形dp (4)1505. 最多 K 次交换相邻数--」树状数组!!

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 。

树状数组:

https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/shu-zhuang-shu-zu-by-liweiwei1419/

树状数组能够实现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(nlog⁡n),初始化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% 的用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值