优秀算法设计与例题(第一部分)

一.双指针

解决数组分块

1.移动零 

283. 移动零 - 力扣(LeetCode)

void moveZeroes(vector<int>& nums)
    {
        int cur=0,dest=0;
        while(cur<nums.size())
        {
            if(nums[cur])
            {
                swap(nums[cur],nums[dest]);
                dest++;
            }
            cur++;
        }
    }

2.复写零

1089. 复写零 - 力扣(LeetCode)

 void duplicateZeros(vector<int>& arr) 
    {
        //1.找到最后一个数
        int cur=0,dest=-1,n=arr.size();
        while(cur<n)
        {
            if(arr[cur])
                dest++;
            else
                dest+=2;
            
            if(dest>=n-1)
                break;
            else
                cur++;
        }

        //2.处理边界情况 1 0 2 3 0 0 4 5
        if(dest==n)
        {
            arr[n-1]=0;
            cur--;
            dest-=2;
        }

        //3.复写
        while(cur>=0)
        {
            if(arr[cur])
            {
                arr[dest--]=arr[cur];
            }
            else
            {
                arr[dest--]=0;
                arr[dest--]=0;
            }
            cur--;
        }
    }

 3.快乐数

202. 快乐数 - 力扣(LeetCode)

class Solution 
{
public:
    int Sum(int n)
    {
        int sum=0;
        while(n>0)
        {
            int x=n%10;
            sum=sum+x*x;
            n=n/10;
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        int slow=n,fast=Sum(n);   //这里的快慢指针并不是真正意义上的指针,而是用具体的数来充当
        while(slow!=fast)
        {
            slow=Sum(slow);
            fast=Sum(Sum(fast));
        }

        if(slow==1)
            return true;
        else
            return false;
    }
};

4.盛水最多的容器

11. 盛最多水的容器 - 力扣(LeetCode)

class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
        int left=0,right=height.size()-1,MAX=0;
        while(left<right)
        {
            int v=min(height[left],height[right])*(right-left);  //直接算出每次遍历的最大值,避免过多的暴力枚举
            MAX=max(v,MAX);

            //移动指针
            if(height[left]>height[right])
                right--;
            else
                left++;
        }
        return MAX;
    }
};

 5.有效三角形的个数

611. 有效三角形的个数 - 力扣(LeetCode)

注意:当三角形的两个小边相加大于第三边 时,也说明可以构成三角形

class Solution 
{
public:
    int triangleNumber(vector<int>& nums) 
    {
        //1.先排序
        sort(nums.begin(),nums.end());

        //2.利用双指针求解
        int sum=0,n=nums.size();
        for(int i=n-1;i>=2;i--)  //固定住最大的数(c)
        {
            int left=0,right=i-1;   //a就是left,b就是right
            while(left<right)
            {
                if(nums[left]+nums[right]>nums[i]) //a+b>c 有right-left个三角形,整个b被用完了,所以right--
                {
                    sum += (right-left);
                    right--;
                }
                else  // a+b<=c 构不成三角形 
                {
                    left++;
                }
            }
        }
        return sum;
    }
};

6.三数之和

15. 三数之和 - 力扣(LeetCode)

 

class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end()); 
        int n=nums.size();
        vector<vector<int>> arr;

        for(int i=0;i<n;)  //固定a
        {
            if(nums[i]>0)break;
            int left=i+1,right=n-1;
            while(left<right)
            {
                int sum=nums[left]+nums[right];
                if(sum+nums[i]==0)
                {
                    arr.push_back({nums[i],nums[left],nums[right]});
                    left++;
                    right--;

                    //去重
                    while(left<right && nums[left]==nums[left-1])  
 //不能是nums[left]==nums[left+1],因为是不能跟前一个数重复,而不是不能跟后一个数重复
                    {
                        left++;
                    }
                    while(left<right && nums[right]==nums[right+1])
//不能是nums[right]==nums[right-1],因为是不能跟前一个数重复,而不是不能跟后一个数重复
                    {
                        right--;
                    }
                    
                }
                else if(sum+nums[i]>0)
                {
                    right--;
                }
                else if(sum+nums[i]<0)
                {
                    left++;
                }
            }

            //去重a
            i++;
            while(i<n && nums[i]==nums[i-1])
            {
                i++;
            }
        }
        return arr;
    }
};

 7.四数之和

18. 四数之和 - 力扣(LeetCode)

class Solution 
{
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        sort(nums.begin(),nums.end());

        int n=nums.size();
        vector<vector<int>> arr;
        for(int i=0;i<n;)
        {
            long long suma=target-nums[i];
            for(int j=i+1;j<n;)
            {
                
                long long sumb=(long long)target-nums[i]-nums[j];
                int left=j+1,right=n-1;
                while(left<right)
                {
                    if(nums[left]+nums[right]==sumb)
                    {
                        arr.push_back({nums[i],nums[j],nums[left],nums[right]});
                        left++;
                        right--;
                        //去重
                        while(left<right && nums[left]==nums[left-1])
                        {
                            left++;
                        }
                        while(left<right && nums[right]==nums[right+1])
                        {
                            right--;
                        }
                    }
                    else if(nums[left]+nums[right]>sumb)
                    {
                        right--;
                    }
                    else if(nums[left]+nums[right]<sumb)
                    {
                        left++;
                    }
                }

                //去重b
                j++;
                while(j<n-2 && nums[j]==nums[j-1])
                {
                    j++;
                }

            }

            //去重a
            i++;
            while(i<n-1 && nums[i]==nums[i-1])
            {
                i++;
            }
        }
        return arr;
    }
};

二.滑动窗口(同向双指针,先满足数组单调性(同正负累加))

1.长度最小的子数组

209. 长度最小的子数组 - 力扣(LeetCode)

class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int n=nums.size(),sum=0;
        int len=n;
        int left=0,right=0;
        for(left=0,right=0;right<n;right++)
        {
            sum += nums[right];  //进窗口
            while(sum>=target)  //判断
            {
                len=min(len,right-left+1);  //更新结果
                sum -= nums[left];      //出窗口
                left++;
            }
        }
        if(left==0)len=0;
        return len;
    }
};

 2.无重复字符的最长字串

3. 无重复字符的最长子串 - 力扣(LeetCode)

class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        int hash[128]={0};  //使用数组来表示哈希表
        int left=0,right=0,len=0;
        int n=s.size();
        while(right<n)
        {
            hash[s[right]]++;  //进窗口
            while(hash[s[right]] > 1)  //判断
            {
                hash[s[left]]--;     //出窗口
				left++;
            }
            len=max(len,right-left+1);
            right++;
        }
        return len;
    }
};

3.最大连续1的个数Ⅲ

1004. 最大连续1的个数 III - 力扣(LeetCode)

 

class Solution
{
public:
    int longestOnes(vector<int>& nums, int k)
    {
        int left = 0, right = 0, sum = 0, n = nums.size(), len = 0;
        while(right < n)  //进窗口
        {
            if (nums[right] == 0)
                sum++;

            while(sum > k)  //判断
            {
                if(nums[left]==0)  //出窗口
                    sum--;
                left++;
            }
            len=max(len,right-left+1);   //计算结果
            right++;
        }
        return len;
    }
};

 4.将X减到0的最小操作数

1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)

class Solution 
{
public:
    int minOperations(vector<int>& nums, int x) 
    {
        int s=0,left=0,right=0,n=nums.size(),len=0;
        for(auto e:nums)
        {
            s += e;
        }
        int target=s-x;
        if(target<0) return -1;
        if(target==0) return n;

        int sum=0;
        while(right<n)
        {
            sum += nums[right];  //进窗口
            right++;
            while(sum>target)  //判断
            { 
                sum -= nums[left];   //出窗口
                left++;  
            }
            if(sum==target)
            {
                len=max(len,right-left);  //更新结果
            }
        }
        if(len==0)
            return -1;
        else 
            return n-len;
    }
};

 5.水果成篮

904. 水果成篮 - 力扣(LeetCode)

class Solution 
{
public:
    int totalFruit(vector<int>& fruits) 
    {
        unordered_map<int,int> hash;   //统计窗口内出现多少种水果,有几个
        int left=0,right=0,n=fruits.size(),len=0;
        while(right<n)
        {
            hash[fruits[right]]++;//进窗口
            right++;
            while(hash.size()>2)  //判断,种类大于2
            {
                hash[fruits[left]]--;  //出窗口
                if(hash[fruits[left]]==0)
                {
                    hash.erase(fruits[left]);   //删除掉first为fruits[left]的数据
                }
                left++;
            }
            len=max(len,right-left);
        }
        return len;
    }
};

6.找到字符串中所有字母异位词

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> ret;
        int hash1[26]={0};  //统计字符串p中每个字符出现的次数
        for(auto ch:p) 
        {
            hash1[ch-'a']++;
        }
        int hash2[26]={0};  //统计窗口里中每个字符出现的次数
        for(int left=0,right=0,count=0;right<s.size();right++)
        {
            hash2[s[right]-'a']++;    //进窗口
            if(hash2[s[right]-'a'] <= hash1[s[right]-'a'])count++;  //count为有效字符个数
            if(right-left+1 > p.size())   //判断
            {
                if(hash2[s[left]-'a'] <= hash1[s[left]-'a'])   //要是有效字符滑出去了
                {
                    count--;
                }
                hash2[s[left]-'a']--;   //出窗口
                left++; 
            }
            //更新结果
            if(count==p.size())
            {
                ret.push_back(left);
            }
        }
        return ret;
    }
};

7.串联所有单词的字串

30. 串联所有单词的子串 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        vector<int> ret;
        unordered_map<string,int> hash1;  //保存words里的单词和频率
        for(auto& s:words) hash1[s]++;

        int len=words[0].size(),m=words.size();
        for(int i=0;i<len;i++)  //执行len次滑动窗口
        {
            unordered_map<string,int> hash2;
            for(int left=i,right=i,count=0;right + len <= s.size();right += len)
            //right + len < s.size() 是为了保证下面单词入哈希表时不越界
            {
                //1.进窗口
                string in=s.substr(right,len);
                hash2[in]++;
                if(hash2[in] <= hash1[in]) count++;   //有效单词

                //2.判断
                if(right - left + 1 > len*m)
                {
                    //3.出窗口
                    string out=s.substr(left,len);
                    if(hash2[out] <= hash1[out])          
                    {
                        count--;  //该出去的这个字符串,在hash2中次数比hash1少,说明是有效字符
                    }
                    hash2[out]--;
                    left += len;
                }
                //4.更新结果
                if(count==m) ret.push_back(left);
            }
        }
        return ret;
    }
};

 8.最小覆盖字串

76. 最小覆盖子串 - 力扣(LeetCode)

class Solution 
{
public:
    string minWindow(string s, string t) 
    {
        int hash1[128]={0};  //128的哈希表适用于英文字符串,hash1用来统计t中每个字符的频次
        int kinds = 0;   //kinds用来统计hash1中有效字符个数
        for(auto ch:t)
        {
            if(hash1[ch]==0)  //ch一定在字符串t里面
            {
                kinds++;
            }
            hash1[ch]++;
        }

        int hash2[128]={0};  //统计窗口内每个字符的频次
        int minlen=INT_MAX , begin=-1;   //因为不知道s和t谁大,所以令minlen=INT_MAX
        for(int left=0,right=0,count=0;right<s.size();right++)
        {
            char in=s[right];       //进窗口
            hash2[in]++; 
            if(hash1[in] == hash2[in]) count++;   //当两个哈希表里的同一字母频次相等时,有效字符加一

            while(count==kinds)  //判断
            {
                if(right-left+1<minlen)  //更新结果
                {
                    minlen=right-left+1;
                    begin=left;
                }
                char out=s[left];  //出窗口
                left++;
                if(hash1[out]==hash2[out]) count--;
                hash2[out]--;
            }
        }
        if(begin==-1) return "";
        else return s.substr(begin,minlen);
    }
};

三.二分查找 

1.二分查找

704. 二分查找 - 力扣(LeetCode)

class Solution 
{
public:
    int search(vector<int>& nums, int target) 
    {
        int left=0,right=nums.size()-1;
        while(left<=right)  //注意是小于等于
        {
            int mid=left+(right-left)/2;   //如果是(left+right)/2 ,会有数据溢出的风险

            if(nums[mid]<target)left=mid+1;
            else if(nums[mid]>target)right=mid-1;    
            else if(nums[mid]==target) return mid;           
        }
        return -1;
    }
};

2.在排序数组中查找元素位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        if(nums.size()==0) return {-1,-1};
        int begin=0;
        //1.二分求左端点
        int left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        } //最后left和right会指向同一点,该点为左端点
        //判断是否有结果
        if(nums[left] != target) return {-1,-1};
        begin=left;  //记录左端点

        //2.二分求右端点
        left=begin,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;   
            //"left+(right-left+1)/2" 与"left+(right-left)/2"仅在个数为偶数时结果不一样,一个算出左边的,一个算出右边的
            if(nums[mid]>target) right=mid-1;
            else left=mid; 
        }
        return {begin,right};
    }
};

         左端点(mid=left+(right-left)/2)求出来mid靠左,所以不能有left=mid,不然会死循环,右端点相反。加一对着减法(right=mid-1)

3.x的平方根

69. x 的平方根 - 力扣(LeetCode)

class Solution
{
public:
    int mySqrt(int x) 
    {
        if(x<1) return 0;
        int left=1,right=x;
        while(left<right)
        {
            long long mid=left+(right-left+1)/2;  //用long long防溢出
            if(mid*mid>x) right=mid-1;
            else left=mid;  //如果是left=mid+1的话,可能会使left指针越过正确的x的位置 
        }
        return left;
    }
};

3.搜索插入位置

35. 搜索插入位置 - 力扣(LeetCode)

class Solution 
{
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        int left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target)left=mid+1;
            else right=mid;
        }
        if(left==nums.size()-1 && nums[left]<target) left++;
        return left;
    }
};

 4.山脉数组的顶峰索引

852. 山脉数组的峰顶索引 - 力扣(LeetCode)

                        只要能被分成两段性质不同的区间的,都可以用二分

class Solution 
{
public:
    int peakIndexInMountainArray(vector<int>& arr) 
    {
        int left=0,right=arr.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(arr[mid]>=arr[mid-1]) left=mid;
            else right=mid-1;
        }
        return left;
    }
};

5.寻找峰值

162. 寻找峰值 - 力扣(LeetCode)

class Solution 
{
public:
    int findPeakElement(vector<int>& nums) 
    {
        int left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>nums[mid+1]) right=mid;
            else if(nums[mid]<nums[mid+1]) left=mid+1;
        }
        return left;
    }
};

6.寻找旋转排序数组的最小值

153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

class Solution 
{
public:
    int findMin(vector<int>& nums) 
    {
        int n=nums.size()-1;
        int left=0,right=n;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>nums[n]) left=mid+1;
            else right=mid;
        }
        return nums[left];
    }
};

四.前缀和 ——>用于快速求出数组中某一连续区间的和O(1)

1.前缀和模板题

【模板】前缀和 (nowcoder.com)

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    //1.读入数据
    int n,q;
    cin>>n>>q;
    vector<int> arr(n+1);
    for(int i=1;i<=n;i++) cin>>arr[i];
    
    //2.预处理出来一个前缀和数组
    vector<long long> dp(n+1);   //防止溢出
    for(int i=1;i<=n;i++) dp[i]=dp[i-1]+arr[i];
    
    //3.使用前缀和数组
    int l=0,r=0;
    while(q--)
    {
        cin>>l>>r;
        cout<<dp[r]-dp[l-1]<<endl;
    }
    return 0;
}


2.二维前缀和

【模板】二维前缀和 (nowcoder.com)

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    //1.读入数据
    int n=0,m=0,q=0;
    cin>>n>>m>>q;
    vector<vector<int>> arr(n+1,vector<int>(m+1)); 
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>arr[i][j];
    
    //2.预处理前缀和矩阵
    vector<vector<long long>> dp(n+1,vector<long long>(m+1));  //防止溢出
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1];
    
    //3.使用dp表
    int x1=0,y1=0,x2=0,y2=0;
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;
        cout<<dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]<<endl;
    }
    return 0;
}

3. 寻找数组的中心下标

724. 寻找数组的中心下标 - 力扣(LeetCode)

class Solution 
{
public:
    int pivotIndex(vector<int>& nums)
    {
        int n=nums.size();
        vector<int> f(n),g(n);  

        //1.预处理前缀和和后缀和数组
        f[0]=0,g[n-1]=0;
        for(int i=1;i<n;i++)
            f[i]=f[i-1]+nums[i-1];
        
        for(int i=n-2;i>=0;i--)
            g[i]=g[i+1]+nums[i+1];

        //2.使用数组
        for(int i=0;i<n;i++)
        {
            if(f[i]==g[i]) return i;
        }
        return -1;
    }
};

4.除自身以外数组的乘积

238. 除自身以外数组的乘积 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> productExceptSelf(vector<int>& nums) 
    {
        int n=nums.size();
        vector<int> f(n),g(n),answer;

        //1.预处理前缀积和后缀积
        f[0]=1,g[n-1]=1;
        for(int i=1;i<n;i++)
            f[i]=f[i-1]*nums[i-1];

        for(int i=n-2;i>=0;i--)
            g[i]=g[i+1]*nums[i+1];

        //2.使用
        for(int i=0;i<n;i++)
        {
            answer.push_back(f[i]*g[i]);
        }
        return answer;
    }
};

5.和为k的子数组

560. 和为 K 的子数组 - 力扣(LeetCode)

class Solution 
{
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        unordered_map<int,int> hash;  //统计前缀和x 和 出现的次数,把前缀和放哈希表里
        hash[0]=1;   //刚开始的时候,前缀和x肯定为0,出现一次

        int sum=0,ret=0;
        for(auto x:nums)
        {
            sum += x;   //计算当前位置的前缀和x
            if(hash.count(sum-k)) //若hash中有前缀和x的值为sum-k的
                ret += hash[sum-k];
            hash[sum]++;  //因为sum出现过,所以sum次数加一
        }
        return ret;
    }
};

6.和可被k整除的子数组

974. 和可被 K 整除的子数组 - 力扣(LeetCode)

class Solution 
{
public:
    int subarraysDivByK(vector<int>& nums, int k) 
    {
        unordered_map<int,int> hash;  //统计前缀和x的余数与次数
        hash[0%k]=1;  //0这个数的余数

        int sum=0,ret=0;
        for(auto x:nums)
        {
            sum += x;  //算出当前的前缀和
            int r=(sum%k+k)%k;  //本来r=sum%k就行,但是现在这样写可以负数的余数变回正数(修正后的余数)
            if(hash.count(r)) ret += hash[r];  //找有几个x前缀和的余数等于r(sum%k)
            hash[r]++;
        }
        return ret;
    }
};

 7.连续数组

525. 连续数组 - 力扣(LeetCode)

class Solution 
{
public:
    int findMaxLength(vector<int>& nums) 
    {
        unordered_map<int,int> hash;  //存的是前缀和x和他的下标j
        hash[0]=-1;  //默认有一个前缀和为0的情况
        for(int i=0;i<nums.size();i++)
            if(nums[i]==0) nums[i]=-1;

        int sum=0,ret=0;
        for(int i=0;i<nums.size();i++)
        {
            sum += nums[i];  //计算当前位置前缀和
            if(hash.count(sum)) ret=max(ret,i-hash[sum]);  
            //如果存在前缀和x等于sum,说明找到了一段前缀和使j+1到i这个区间和为0,长度为i-(j+1)+1
            else hash[sum]=i; //如果不存在,那么放入把值为sum的前缀和x存入哈希表,下标为i
        }
        return ret;
    }
};

8.矩阵区域和

1314. 矩阵区域和 - 力扣(LeetCode)

class Solution 
{
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) 
    {
        int m=mat.size(),n=mat[0].size(); 
        //1.预处理一个前缀和矩阵
        vector<vector<int>> dp(m+1,vector<int>(n+1)); //dp的行列各加一,防止越界到-1
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i-1][j-1]; 
                //mat[i-1][j-1] ,而不是mat[i][j] 

        //2.使用前缀和矩阵
        vector<vector<int>> ans(m,vector<int>(n));  //ans与mat同等规模篇
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                int x1=max(0,i-k)+1;  //从mat到dp减了1,从dp变回ans还要再加回去
                int y1=max(0,j-k)+1;
                int x2=min(m-1,i+k)+1;
                int y2=min(n-1,j+k)+1;
                ans[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
            }
        }
        return ans;
    }
};

五.位运算

1.基础运算

191. 位1的个数 - 力扣(LeetCode)

class Solution 
{
public:
    int hammingWeight(uint32_t n) 
    {
        int sum=0;
        for(int i=0;i<32;i++)
        {
            if(((n>>i)&1)==1)
                sum++;
        }
        return sum;
    }
};

 338. 比特位计数 - 力扣(LeetCode)

class Solution 
{
public:
    int one_number(int x)
    {
        int sum=0;
        for(int i=0;i<32;i++)
        {
            if(((x>>i)&1)==1) sum++;
        }
        return sum;
    }

    vector<int> countBits(int n) 
    {
        vector<int> ans;
        for(int i=0;i<=n;i++)
        {
            ans.push_back(one_number(i));
        }
        return ans;
    }
};

461. 汉明距离 - 力扣(LeetCode)

class Solution 
{
public:
    int hammingDistance(int x, int y) 
    {
        int m=x^y;
        int sum=0;
        for(int i=0;i<32;i++)
        {
            if(((m>>i)&1)==1)
                sum++;
        }
        return sum;
    }
};

136. 只出现一次的数字 - 力扣(LeetCode)

class Solution 
{
public:
    int singleNumber(vector<int>& nums) 
    {
        int sum=0;
        for(int i=0;i<nums.size();i++)
        {
            sum = sum ^ nums[i];
        }
        return sum;
    }
};

260. 只出现一次的数字 III - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        long long sum=0;
        for(auto x:nums) sum ^= x;  
        //这个异或值必定不为0,所以要找的那两个数一定有某一位是1和0,所以分成两组,每组各有一个目标值
        unsigned int mark = (unsigned int)(sum & (-sum)); //mark为sum里最右侧的1 (00000010)
        vector<int> ans(2);

        for(auto x:nums)
        {
            if((unsigned int)(x & mark) == 0)  //如 10010101 & 00000010 ==0
                ans[0] ^= x;
            else
                ans[1] ^= x;
        }
        return ans;
    }
};

2.判断字符是否唯一

面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

class Solution 
{
public:
    bool isUnique(string astr) 
    {
        if(astr.size()>26) return false;

        int bitMap=0;   //建立位图
        for(auto ch:astr)
        {
        int i=ch-'a';
        //判断字符是否出现过
        if(((bitMap>>i)&1)==1) return false;
        bitMap |= (1<<i);  //把字符加入位图
        }
        return true;
    }
};

3.丢失的数字

268. 丢失的数字 - 力扣(LeetCode)

class Solution 
{
public:
    int missingNumber(vector<int>& nums) 
    {
        int sum=0;
        int n=nums.size();
        for(int i=0;i<n;i++)
        {
            sum = sum ^ i ^ nums[i];
        }
        sum = sum ^ n;
        return sum;
    }
};

4.两整数之和

371. 两整数之和 - 力扣(LeetCode)

class Solution 
{
public:
    int getSum(int a, int b) 
    {
        while(b)
        {
            int x=a^b;  //算出无进位相加结果
            b=(unsigned int)(a&b)<<1;  
            //算出进位,注意-1要转成unsigned int,-1的第一个1意义不明确
            a=x;
        }
        return a;
    }
};

5.只出现一次的数字Ⅱ

137. 只出现一次的数字 II - 力扣(LeetCode)

class Solution 
{
public:
    int singleNumber(vector<int>& nums) 
    {
        int ret=0;
        for(int i=0;i<32;i++)  //对结果的每一位分别进行修改
        {
            int sum=0;
            for(auto x:nums)
                if(((x>>i)&1)==1)
                    sum++;

            if(sum%3==0) ret &= (~(1<<i));  //将第i位改成0
            else ret |= (1<<i);   //将第i位改成1
        }
        return ret;
    }
};

6.消失的两个数字

面试题 17.19. 消失的两个数字 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> missingTwo(vector<int>& nums) 
    {
        int sum=0;
        int n=nums.size();
        //1.将所有事数异或在一起
        for(int i=0;i<n;i++)
            sum=sum^nums[i]^(i+1);
        sum=sum^(n+1)^(n+2);

        //2.找到不同位
        int mark=sum&(-sum);

        //3.分两类来异或
        vector<int> ans(2);
        for(auto x:nums)
        {
            if((x&mark)==0) ans[0] ^= x;
            else ans[1] ^= x;
        }
        for(int i=1;i<=n+2;i++)
        {
            if((i&mark)==0) ans[0] ^= i;
            else ans[1] ^= i;
        }
        return ans;
    }
};

 六.模拟(比葫芦画瓢)

1.替换所有问号

1576. 替换所有的问号 - 力扣(LeetCode)

class Solution 
{
public:
    string modifyString(string s) 
    {
        int n=s.size();
        for(int i=0;i<n;i++)
        {
            if(s[i]=='?')
            {
                //修改s[i]
                for(char ch='a';ch<='z';ch++)
                {
                    if(i==0 && ch!=s[i+1]) 
                    {
                        s[i]=ch;
                        break;
                    }
                    if(i==n-1 && ch!=s[i-1])
                    {
                        s[i]=ch;
                        break;
                    } 
                    if(ch!=s[i+1] && ch!=s[i-1])  
                    {
                        s[i]=ch;
                        break;
                    }
                }
            }
        }
        return s;
    }
};

2.提莫攻击

495. 提莫攻击 - 力扣(LeetCode)

class Solution 
{
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) 
    {
        int ret=0;
        for(int i=1;i<timeSeries.size();i++)
        {
            if(timeSeries[i]-timeSeries[i-1] >= duration) ret += duration;
            else ret += timeSeries[i]-timeSeries[i-1];
        }
        return ret+duration;
    }
};

3.Z字形变换

6. Z 字形变换 - 力扣(LeetCode)

class Solution 
{
public:
    string convert(string s, int numRows) 
    {
        string ret;
        int n=s.size();
        if(numRows==1) return s;
        int d=2*numRows-2;
        for(int i=0;i<numRows;i++)
        {
            if(i==0)     //处理第一行
                for(int k=0,j=i;j+k*d<n;k++)
                    ret.push_back(s[j+k*d]);
            else if(i==numRows-1)  //处理最后一行
                for(int k=0,j=i;j+k*d<n;k++)
                    ret.push_back(s[j+k*d]);
            else
            {   //处理中间行
                for(int k=0,j=i;j+k*d<n;k++)
                {
                    ret.push_back(s[j+k*d]);
                    if((d-i+k*d)<n)
                        ret.push_back(s[d-i+k*d]);
                }
            }
        }
        return ret;
    }
}; 

4. 外观数列

38. 外观数列 - 力扣(LeetCode)

class Solution 
{
public:
    string countAndSay(int n) 
    {
        string ret="1";
        for(int i=1;i<n;i++)  //描述n-1次
        {
            int left=0,right=0,count=0;
            string ans;
            for(left=0,right=0;right<ret.size();)  //用双指针分组计数
            {
                while(right<ret.size() && ret[left]==ret[right]) right++;
                count=right-left;
                ans += to_string(count) + ret[left];
                left=right;
            }
            ret=ans;
        }
        return ret;
    }
};

5.数青蛙

1419. 数青蛙 - 力扣(LeetCode)

class Solution 
{
public:
    int minNumberOfFrogs(string croakOfFrogs) 
    {
        string t="croak";
        int n=t.size();
        vector<int> hash(n);   //数组哈希表

        unordered_map<char,int> index;  //存[x,x这个字符对应的下标]
        for(int i=0;i<n;i++) index[t[i]]=i;  //一般都是通过下标找数据,这回反过来了

        for(auto ch:croakOfFrogs)
        {
            if(ch=='c') 
            {
                if(hash[n-1] != 0) hash[n-1]--;
                hash[0]++;
            }
            else
            { 
                int i=index[ch];  
                if(hash[i-1]==0) return -1;

                hash[i-1]--;
                hash[i]++;
            }
        }
        
        for(int i=0;i<=n-2;i++)
            if(hash[i] != 0) return-1;   //表不合法
        
        return hash[n-1];
    }
};

七.分治 

1.颜色分类

75. 颜色分类 - 力扣(LeetCode)

class Solution 
{
public:
    void sortColors(vector<int>& nums) 
    {
        int n=nums.size();
        int left=-1,right=n,i=0;
        while(i!=right)
        {
            if(nums[i]==1) i++;
            else if(nums[i]==0)
            {
                left++;
                swap(nums[left],nums[i]);
                i++;
            }
            else if(nums[i]==2)
            {
                right--;
                swap(nums[right],nums[i]);
            }
        }
    }
};

 2.排序数组(数组分块,快排)

 912. 排序数组 - 力扣(LeetCode)

class Solution 
{
public:
    int getRandom(vector<int>& nums,int left, int right)
    {
        int r=rand();
        return nums[r%(right-left+1)+left];
    }

    void my_qsort(vector<int>& nums,int l,int r)
    {
        if(l>=r) return;  //递归结束条件

        //数组分三块 
        int key=getRandom(nums,l,r);   //根据这个随机数把数组分三块 小于key,等于key,大于key
        int i=l,left=i-1,right=r+1;  //left在区间最左侧外面一个,right在最右侧
        while(i<right)
        {
            if(nums[i]<key)
            {
                left++;
                swap(nums[left],nums[i]);
                i++;
            }
            else if(nums[i]==key) i++;
            else
            {
                right--;
                swap(nums[right],nums[i]);
            }
        }
        //分完之后: [l,left],[left+1,right-1],[right,r]
        //接下来再排左右两边
        my_qsort(nums,l,left);  //排左边
        my_qsort(nums,right,r);  //排右边
    }

    vector<int> sortArray(vector<int>& nums) 
    {
        srand(time(NULL));   //种下时间种子
        my_qsort(nums,0,nums.size()-1);
        return nums;
    }
};

3.数组中的第k个最大元素(快速选择算法)

215. 数组中的第K个最大元素 - 力扣(LeetCode)

class Solution 
{
public:
    int getRandom(vector<int>& nums,int left,int right)
    {
        int r=rand();
        return nums[r%(right-left+1)+left];
    }

    int my_qsort(vector<int>& nums,int l,int r,int k)
    {
        if(l==r) return nums[l];  //结束条件

        //1.随机选择基准元素
        int key=getRandom(nums,l,r);

        //2.根据基准元素将数组分三块
        int i=l,left=l-1,right=r+1;
        while(i<right)
        {
            if(nums[i]<key)
            {
                left++;
                swap(nums[left],nums[i]);
                i++;
            }
            else if(nums[i]==key) i++;
            else
            {
                right--;
                swap(nums[right],nums[i]);
            }
        }
        //分成[l,left] [left+1,right-1] [right,r]
        //3.分情况讨论
        int c=r-right+1;   //b,c为区间的元素个数
        int b=right-left-1;

        if(c>=k) return my_qsort(nums,right,r,k);
        else if(b+c>=k) return key;    //不在c中,只能在b里了
        else return my_qsort(nums,l,left,k-b-c);   //只能去a中找第k-b-c大的了
    }

    int findKthLargest(vector<int>& nums, int k) 
    {
        srand(time(NULL));
        return my_qsort(nums,0,nums.size()-1,k);        
    }
};

4.最小的K个数(快速选择算法)

面试题 17.14. 最小K个数 - 力扣(LeetCode)

class Solution 
{
public:
    int getRandom(vector<int>& arr,int left,int right)
    {
        return arr[rand()%(right-left+1)+left];
    }

    void my_qsort(vector<int>& arr,int l,int r,int k)
    {
        if(l>=r) return;

        //1.数组分三块
        int key=getRandom(arr,l,r);
        int i=l,left=l-1,right=r+1;
        while(i<right)
        {
            if(arr[i]<key)
            {
                left++;
                swap(arr[left],arr[i]);
                i++;
            }
            else if(arr[i]==key) i++;
            else
            {
                right--;
                swap(arr[right],arr[i]);
            }
        }
        //[l,left],[left+1,right-1],[right,r]
        //2.分情况讨论
        int a=left-l+1,b=right-left-1;
        if(a>=k) my_qsort(arr,l,left,k);   //只需要给左边排序就行(只排第一块)
        else if(a+b>=k) return ;           //k>a && a+b>=k,已经排好了,不用再排了,
        else my_qsort(arr,right,r,k-a-b);  //只需要去右边在排k-a-b个就行
    }

    vector<int> smallestK(vector<int>& arr, int k) 
    {
        srand(time(NULL));
        my_qsort(arr,0,arr.size()-1,k);
        return {arr.begin(),arr.begin()+k};
    }
};

5.排序数组(归并排序)

912. 排序数组 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> tmp;     //使用全局变量构造辅助数组,节省空间
    void mergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return ;

        //1.选择中间点划分区间
        int mid=(right-left)/2+left;
        //[left,mid] [mid+1,right]

        //2.排序左右区间
        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);

        //3.合并两个有序数组 
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid && cur2<=right)
        {
            tmp[i++]=(nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++]);
        }
        //处理没有遍历完的数组
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];

        //4.还原
        for(int i=left;i<=right;i++)
            nums[i]=tmp[i-left];
    }

    vector<int> sortArray(vector<int>& nums) 
    {
        tmp.resize(nums.size());
        mergeSort(nums,0,nums.size()-1);
        return nums;
    }
};

6.逆序对个数

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

class Solution 
{
    int tmp[50001];
public:
    int mergesort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return 0;

        int ret=0;
        //1.找中间点,将数组分为两部分 [left,mid][mid+1,right]
        int mid=(right-left)/2+left;

        //2.求左边个数+排序 求右边个数+排序
        ret += mergesort(nums,left,mid);
        ret += mergesort(nums,mid+1,right);

        //3.再求一左一右的个数 (递归的主逻辑) ,还要排序
        int cur1=left,cur2=mid+1,i=0;
        while((cur1<=mid) && (cur2<=right))
        {
            if(nums[cur1] > nums[cur2]) 
            {
                ret += mid-cur1+1;
                tmp[i++]=nums[cur2++];   //别忘了还得排序,计算个数不过是在排序中进行的
            }
            else 
            {
                tmp[i++]=nums[cur1++];
            }
        }

        //4.处理剩下的元素
        while(cur1 <= mid) tmp[i++]=nums[cur1++];
        while(cur2 <= right) tmp[i++]=nums[cur2++];
        for(int j=left;j<=right;j++)
            nums[j]=tmp[j-left];

        return ret;
    }

    int reversePairs(vector<int>& record) 
    {
        return mergesort(record,0,record.size()-1);
    }
};

7.计算右侧小于当前元素的个数

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> ret;
    vector<int> index;  //记录nums中当前元素的原始下标
    int tmpNums[500001];  //两个辅助数组
    int tmpIndex[500001];

    void mergesort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return;

        //1.根据中间元素,分成两块 [left,mid] [mid+1,right]
        int mid=(right-left)/2+left;
        
        //2.先处理左右两边
        mergesort(nums,left,mid);
        mergesort(nums,mid+1,right);

        //3.处理主逻辑(一左一右)
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid && cur2<=right)
        {
            if(nums[cur1]>nums[cur2])
            {
                ret[index[cur1]] += right-cur2+1;  //index[cur1]就是cur1的下标
                tmpNums[i]=nums[cur1];
                tmpIndex[i++]=index[cur1++];  //元素和对应的下标都要放到辅助数组里面去
            }
            else
            {
                tmpNums[i]=nums[cur2];
                tmpIndex[i++]=index[cur2++];
            }
        }

        //4.处理剩下的排序
        while(cur1<=mid)
        {
            tmpNums[i]=nums[cur1];
            tmpIndex[i++]=index[cur1++];
        }
        while(cur2<=right)
        {
            tmpNums[i]=nums[cur2];
            tmpIndex[i++]=index[cur2++];
        }

        //5.还原
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmpNums[j-left];
            index[j]=tmpIndex[j-left];
        }
    }

    vector<int> countSmaller(vector<int>& nums) 
    {
        int n=nums.size();
        ret.resize(n);
        index.resize(n);

        //初始化下标数组
        for(int i=0;i<n;i++) index[i]=i;

        mergesort(nums,0,n-1);
        return ret;
    }
};

8.翻转对

493. 翻转对 - 力扣(LeetCode)

class Solution 
{
public:
    int tmp[50001];
    int mergesort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return 0;

        int ret=0;
        //1.先根据中间元素划分区间 [left,mid] [mid+1,right]
        int mid=(right-left)/2+left;

        //2.计算左右翻转对的数量
        ret += mergesort(nums,left,mid);
        ret += mergesort(nums,mid+1,right);

        //3.计算翻转个数的主逻辑 
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid)  //固定cur1看cur2
        {
            while(cur2<=right && nums[cur1]/2.0<=nums[cur2]) cur2++;  //*2可能会溢出,用/2.0就可以了
            if(cur2 > right) break;

            ret += right-cur2+1;   //此时nums[cur1]>2*nums[cur2]
            cur1++;
        }

        //4.合并两个有序数组(归并排序的主逻辑)  ---排降序
        cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid && cur2<=right) 
            tmp[i++]=(nums[cur1]>=nums[cur2] ? nums[cur1++] : nums[cur2++]);
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];

        //5.还原
        for(int j=left;j<=right;j++)
        {
            nums[j]=tmp[j-left];
        } 
        return ret;
    }

    int reversePairs(vector<int>& nums) 
    {
        return mergesort(nums,0,nums.size()-1);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对玛导至昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值