Bootstrap

双指针算法-day6

1.数的乘积等于两数乘积的方法数

题目

解析

1.解析

  • 先将数组排序,然后针对第一个数组每个数进行遍历,第二个数组用双指针计算数量;
  • 这题很容易爆 int,所以要用 long / long long 存储平方数或乘积;
  • 如果小于目标值,直接 L++;大于目标值,直接 R--;
  • 如果等于目标值,且左数等于右数,说明 L-R 均相等,计算 C(R - L + 1, 2) 即可;
  • 如果左数不等于右数,则左右指针在等于前数情况下,分别向内移动,计算 (L1 - L) * (R1 - R);
  • 优化:最大数平方小于目标,直接 break;最小数平方大于目标,直接 continue;
  • 时间复杂度:O(m*n);空间复杂度:O(1);

代码

class Solution {
public:
    int numTriplets(vector<int>& nums1, vector<int>& nums2) {
        // 时间复杂度:O(mn)
        // 空间复杂度:O(1)

        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());

        return cal(nums1, nums2) + cal(nums2, nums1);
    }

    int cal(vector<int>& nums1,vector<int>& nums2){
        int ans = 0;
        
        for(int i = 0;i < nums1.size();i ++){
            long target = (long)nums1[i] * nums1[i];// 第一个数组目标乘积

            int l = 0,r = nums2.size() - 1;

            // 不满足情况: 最大乘积小于目标 / 最小乘积大于目标
            if ((long)nums2[r] * nums2[r] < target) break;
            else if ((long)nums2[l] * nums2[l] > target) continue;
            
            // 双指针
            while(l < r){
                long s = (long)nums2[l] * nums2[r];// 第二个数组计算乘积

                if(s > target) r --;
                else if(s < target) l ++;
                else {
                    if(nums2[l] == nums2[r]){ // l - r 都相等,任意两个数相乘都满足
                        ans += (r - l + 1) * (r - l) / 2;
                        break;
                    }else{
                        int cnt1 = 0,x = nums2[l];
                        int cnt2 = 0,y = nums2[r];
                        while(l <= r && nums2[l] == x){ // 注意边界问题
                            cnt1 ++;
                            l ++; 
                        }
                        while(l <= r && nums2[r] == y){
                            cnt2 ++;
                            r --;
                        }
                        ans += cnt1 * cnt2;
                    }
                }
            }
        }

        return ans;
    }
};

2.三数之和的多种可能

题目

解析

1.解析

  • 这题就是《三数之和》及上面这题的结合版,用双指针,讨论左数与右数的关系;
  • 左数等于右数,说明 L-R 均相等,计算 C(R - L + 1, 2) 即可;
  • 如果左数不等于右数,则左右指针在等于前数情况下,分别向内移动,计算 (L1 - L) * (R1 - R);特别注意:指针移动的循环一定要加上 L <= R;
  • 时间复杂度:O(n ^ 2);空间复杂度:O(1);

代码

class Solution {
public:
    int threeSumMulti(vector<int>& arr, int target) {
        // 时间复杂度:O(n ^ 2)
        // 空间复杂度:O(1)

        sort(arr.begin(),arr.end());
        int n = arr.size();
        int ans = 0;
        int mod = 1e9 + 7;

        for(int i = 0;i < n-2;i ++){
            int x = target - arr[i]; // 转换为双指针问题

            int l = i + 1,r = n-1;
            while(l < r){
                if(arr[l] + arr[r] < x) l ++;
                else if(arr[l] + arr[r] > x) r --;
                else{
                    if(arr[l] == arr[r]) { // l - r 都相等,任意两个数相加都满足
                        ans = (ans + (r - l + 1) * (r - l) / 2) % mod;
                        break;
                    }
                    else{ 
                        int cnt1 = 0, x = arr[l];
                        int cnt2 = 0, y = arr[r];
                        while (l <= r && x == arr[l]) { // 注意边界问题
                            cnt1++;
                            l++;
                        }
                        while (l <= r && y == arr[r]) {
                            cnt2++;
                            r--;
                        }

                        ans = (ans + cnt1 * cnt2) % mod; 
                    }
                }
            }
        }

        return ans;
    }
};

3.令牌放置

题目

解析

1.解析

  • 根本目的:尽可能得分,也就是尽可能多的翻得分牌;
  • 注意:没有得分牌 / 积分不够翻最小的得分牌时,返回0;
  • 有积分且足够翻得分牌,则直接翻得分牌;积分不够,就判断有没有得分,若有得分,就消耗1得分翻最大积分牌;就这样不断循环,只要翻了得分牌,就更新最大得分;
  • 时间复杂度:O(n);空间复杂度:O(1);

代码

class Solution {
public:
    int bagOfTokensScore(vector<int>& tokens, int power) {
        // 时间复杂度:O(n)
        // 空间复杂度:O(1)

        sort(tokens.begin(),tokens.end());
        int ans = 0,score = 0;
        int n = tokens.size();

        // 没有得分牌可翻/积分不够翻最小的得分牌
        if(tokens.empty() || power < tokens[0]) return 0; 

        int l = 0,r = n-1;

        // 双指针,根本目标:翻更多的得分牌
        while(l <= r){
            if(power >= tokens[l]) { // 积分足够翻得分牌
                power -= tokens[l];
                l ++;
                score ++;
                ans = max(ans,score);
            }else {
                if(score >= 1){ // 积分不够翻得分牌,就看又没有得分去翻积分牌
                    power += tokens[r];
                    r --;
                    score --;
                }
            }
        }

        return ans;
    }
};

4.分割两个字符串得到回文串

题目

解析

1.暴力解析:创建两个字符串存储拼接结果,内存超了;

2.解析

  • 分别设置两个指针在数组 a, b 的首端,尾端,向内不断扩展直到两数不相等;
  • 设此时分别指向 L,R,则 0 - L 和 R - (n-1) 一定能构成回文串,接下来就是判断 L-R 这一段字符是否为回文串;
  • 第一个两种情况,分别为数组 a,b 提供中间字符段,只要一个满足是回文串就返回 true;
  • 第二个两种情况,分别为数组 a,b 提供前 / 后字符段,满足一个也返回 true;
  • 时间复杂度:O(n);空间复杂度:O(1);

代码

class Solution {
public:
    // 判断 s[i] 到 s[j] 是不是回文串
    bool isPalindrome(string &s,int i,int j){
        while(i <= j && s[i] == s[j]){
            i ++;
            j --;
        }
        return i > j;
    }

    // 判断数组a 的前段序列和数组b 的后段序列能否构成回文串
    bool check(string &a,string &b){
        int l = 0,r = a.size()-1;

        // 找到 a 和 b 数组可以匹配的最长字符段
        while(l < r && a[l] == b[r]){
            l ++;
            r --;
        }

        // 剩余部分 l - r 由数组a / 数组b 其中一个给出,判断是否为回文串 
        return isPalindrome(a,l,r) || isPalindrome(b,l,r);
    }

    bool checkPalindromeFormation(string a, string b) {
        // 时间复杂度:O(n)
        // 空间复杂度:O(1)

        return check(a,b) || check(b,a);// 考虑哪个数组提供前序列
    }
};

;