LeetCode专项刷题记录{持续更新}

LeetCode专项刷题记录

  • 1 数组
    • 1.0 数字区间内部按位与
      • 1.0.1 区间长度大于2^k,则后k位与成0
      • 1.0.2 寻找公共前缀
      • 1.0.3 右侧减一取与
    • 1.1 寻找中心数组索引:循环移位
      • 1.1.1 C++
      • 1.1.2 Python
    • 1.2 搜索插入位置:二分查找
      • 1.2.1 C++
    • 1.3 合并区间:
      • 1.3.1 C++
    • 1.4 无重叠区间:
      • 1.4.1 (参考思想:最大上升子序列)动态规划
      • p.s.:最大上升子序列
      • 1.4.2 贪心算法
  • 2. 二维数组
    • 2.1 旋转矩阵
      • 2.1.1 C++:非原地旋转
    • 2.2 零矩阵
      • 2.2.1 C++(两次循环)
    • 2.3 ZIG-ZAG
      • C++
  • 3. 字符串
    • 3.0 #中包含的字符串操作
    • 3.1 最长公共前缀:.substr[op,ed)活用
      • 3.1.1 横向扫描
      • 3.1.2 纵向扫描
      • 3.1.3 分而治之
    • 3.2* 回文子串数量
    • 3.2 最长回文子串:
      • 3.2.1 中心扩散
      • 3.2.2 动态规划
    • 3.3 翻转字符串中的单词
      • 3.3.1 双指示符辅助,外部翻转
      • 3.3.2 (感觉好麻烦...)
      • 3.3.3 大佬方法:使用字符串流
  • 4. 双指针
    • 4.1 翻转字符串
    • 4.2 给定有序数组,寻找两数之和=目标数
      • 4.2.1 从头开始遍历左值并对右值进行二分查找
      • 4.2.2 双端指针向内逼近
    • 4.3 快慢指针
      • 4.3.1 移除数组中所有指定元素
      • 4.3.2 最大连续1的个数(单次循环即可)
      • 4.3.3 长度最小的子数组
  • 5. 队列
    • 循环队列操作

链接: https://leetcode-cn.com/leetbook/read/.

1 数组

1.0 数字区间内部按位与

leetcode201
给定范围 [m, n],其中 0 <= m <= n <= 2147483647(2^2-31,即INT_MAX),
返回此范围内所有数字的按位与(包含 m, n 两端点)。
输入: [5,7]
输出: 4

1.0.1 区间长度大于2^k,则后k位与成0

class Solution {
     
public:
    int rangeBitwiseAnd(int m, int n) {
     
    	// 排除移位误操作
        if(m == 0 && n == 2147483647) return m;
        // 求取区间长度的2对数
        int len = n - m + 1, k = ceil(log(len) / log(2));
        // 区间头尾互与,先右移出再左还原
        return (m&n)>>(k)<<(k);
    }
};

1.0.2 寻找公共前缀

class Solution {
     
public:
    int rangeBitwiseAnd(int m, int n) {
     
        int shift = 0;
        // 找到公共前缀{当m=n)
        while (m < n) {
     
            m >>= 1;
            n >>= 1;
            ++shift;
        }
        return m << shift;
    }
};

1.0.3 右侧减一取与

class Solution {
     
public:
    int rangeBitwiseAnd(int m, int n) {
     
        while (m < n) {
     
            // 抹去最右边的 1,Brian算法
            n = n & (n - 1);
        }
        return n;
    }
};

1.1 寻找中心数组索引:循环移位

给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。
我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
输入:nums = [1, 2, 3]
输出:-1

1.1.1 C++

class Solution {
     
public:
    int pivotIndex(vector<int>& nums) {
     
        int len = nums.size();
        if(len < 2){
     
            return -1;
        }
        int suml = 0;
        int sumr = 0;
        // 可以从零开始,特例:[2,0],[0,2]
        for(int i=0; i<len; i++){
     
            sumr += nums[i];
        }
		// 法一:删一,比较,添一
        for(int i=0; i<len; i++){
     
            sumr -= nums[i];
            if(suml == sumr){
     
                return i;
            }
            suml += nums[i];
        }
        // 法二:翻倍加中心比较,添一
        for(int i=0; i<len; i++){
     
            if(suml*2 + nums[i] == sumr){
     
                return i;
            }
            suml += nums[i];
        }
        return -1;
    }
};

1.1.2 Python

class Solution(object):
    def pivotIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        suml = 0
        S = sum(nums)
        # enumerate 取序号和值
        for i, x in enumerate(nums):
            if suml == (S-x-suml):
                return i
            suml += x
        return -1

1.2 搜索插入位置:二分查找

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
可以假设数组中无重复元素。
输入: [1,3,5,6], 5
输出: 2
输入: [1,3,5,6], 7
输出: 4
输入: [1,3,5,6], 0
输出: 0

1.2.1 C++

class Solution {
     
public:
    int searchInsert(vector<int>& nums, int target) {
     
        int len = nums.size();
        // 初始判断,可不加
        if(len == 0) return 0;
        int left = 0;
        int right = len-1;
        // 二分查找通用模板
        while(left <= right){
     
        	// 取中值
            int mid = left +((right-left)>>1);
            // 符合则输出
            if(nums[mid] == target)  return mid;
            // 过大则取左
            if(nums[mid] > target){
     
                right = mid-1;
                continue;
            }
            // 过小则取右
            if(nums[mid] < target){
     
                left = mid+1;
                continue;
            }
        }
        // 取左侧点
        //(因为循环条件。取左:会让右=左,实现右插入;取右:则不能左插入)
        return left;
    }
};

1.3 合并区间:

给出一个区间的集合,请合并所有重叠的区间。
约束:区间左界不大于右界
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

1.3.1 C++

(法一:比较算法:简易压入)

class Solution {
     
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
     
        // 保存最后结果和单位结果存储buff
        vector<vector<int>> results;
        vector<int> ele(2, 0);
        int len = intervals.size();
        // 容器为空,返回空集{}
        if(len==0) return {
     };
        // 容器尺寸为1,返回原值
        if(len==1) return intervals;
        // 容器排序(按容器单元内最左值)
        sort(intervals.begin(), intervals.end());
        // 将第0个区间样本放入buff
        ele = intervals[0];
        // 开始区间合并处理
        for(int i=1; i<len; i++){
     
            // 内右大于等于外右
            if(ele[1]>=intervals[i][1]){
     
                continue;
                // 内右大于等于外左,合并保存
            }else if(ele[1]>=intervals[i][0]){
     
                ele[1] = intervals[i][1];
                // 内右小于外左,压入更新
            }else if(ele[1]<intervals[i][0]){
     
                results.push_back(ele);
                ele = intervals[i];
            }
        }
        // 将最后一个区间压入
        results.push_back(ele);
        return results;
    }
};

(法二:比较算法:简易压入)

class Solution {
     
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
     
        if (intervals.size() == 0) {
     
            return {
     };
        }
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> merged;
        for (int i = 0; i < intervals.size(); ++i) {
     
        	// 取左右值
            int L = intervals[i][0], R = intervals[i][1];
            // merge非空 或者 merge尾项右值 不包括 当前左值
            if (!merged.size() || merged.back()[1] < L) {
     
                // 压入
                merged.push_back({
     L, R});
            }
            else {
     
            	// merge尾项右值更新
                merged.back()[1] = max(merged.back()[1], R);
            }
        }
        return merged;
    }
};

(法三:桶排序:垃圾算法的空间复杂度爆炸)

class Solution {
     
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
     
        sort(intervals.begin(), intervals.end());
        int len = intervals.size();
        vector<vector<int> > results;
        vector<int> ele(2, 0);
        // if(len == 0) return results.push_back(ele);
        if(len == 1) return intervals;
        int start = intervals[0][0];
        int limit = intervals[len-1][1];
        vector<int> bottle(limit+1, 0);
        for(int i=0; i<len; i++){
     
            for(int j=intervals[i][0]; j<=intervals[i][1]; j++){
     
                bottle[j] = 1;
            }
        }
        int flag = 0;
        for(int i=start; i<=limit; i++){
     
            if(flag==0 && bottle[i]==1){
     
                ele[0] = i;
                flag = 1;
            }
            if(flag==1 && bottle[i]==0){
     
                ele[1] = i-1;
                flag = 0;
                results.push_back(ele);
            }
            if(flag==1 && bottle[i]==1 && i==limit){
     
                ele[1] = limit;
                results.push_back(ele);
            }
        }
        return results;
    }
};

1.4 无重叠区间:

1.4.1 (参考思想:最大上升子序列)动态规划

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

class Solution {
     
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
     
        // 走个流程
        int len = intervals.size();
        if(len == 0) return 0;
        // sort排序,(适用范围:数组、链表、向量)
        // 参数(首地址,首地址+长度)
        sort(intervals.begin(), intervals.end());
        // 记录:至i处的最多不重叠区间树
        vector<int> dp(len, 1);
        int ans = 1;
        // 开始记录各dp:
        for(int i=1; i<len; i++){
     
            // 比较i与i之前所有区间是否重叠
            for(int j=0; j<i; j++){
     
                // 不重叠则比较当前dp[i]与dp[j]+1,取较大
                if(intervals[i][0] >= intervals[j][1]){
     
                    dp[i] = max(dp[i], dp[j]+1);
                    // [[4,5],[5,6],[6,7],[1,2],[2,3],[7,8]]
                    //    1     2     3     1     2     4(不会被更新成3)
                    ans = max(dp[i], ans);
                }
            }
        }
        // 最小剔除个数 == 原区间数 - 最大不重叠区间个数
        return len-ans;
    }
};

p.s.:最大上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

class Solution {
     
public:
    int lengthOfLIS(vector<int>& nums) {
     
        int len = nums.size();
        if(len==0) return 0;

        vector<int> dp(len, 1);
        int ans = 1;
        for(int i=1; i<len; i++){
     
            for(int j=0; j<i; j++){
     
                if(nums[i]>nums[j]){
     
                    dp[i] = max(dp[i], dp[j]+1);
                    ans = max(ans, dp[i]);
                }
            }
        }
        return ans;
    }
};

1.4.2 贪心算法

  1. 无重叠区间尽可能地多
  2. 前一区间若包含后一区间,则删除前一区间
  3. 前一区间若与后一区间部分重叠,则删除后一区间
  4. 前一区间若与后一区间不重叠,则保存前一区间
/*解法二:贪心法*/
// 定义sort升序规则
bool cmp(vector<int> vec1,vector<int> vec2){
     
    return vec1[0]<vec2[0];
}

class Solution {
     
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
     
        // 按照起始点从小到大排序
        std::sort(intervals.begin(),intervals.end(),cmp);
        // 存放不重叠的区间
        vector<vector<int>> Temp;
        // 记录与Temp不重叠的备选压入区间
        int last=0;
        
        for(int i=1;i<intervals.size();++i){
     
            // 若备选区间与新区间不重叠
            if(intervals[last][1]<=intervals[i][0]){
     
                // 压入备选区间(判断不重叠才会被压入)
                Temp.push_back(intervals[last]);
                // 刷新尾部号(新区间成为备选区间)
                last=i;
            }
            else if(intervals[last][1]>=intervals[i][1]){
     
                // 若备选区间包含新区间
                // 原备选区间被新区间所替代,继续观察
                last=i;
            }             
        }
        // 备选区间转正
        if(last<intervals.size())
            Temp.push_back(intervals[last]);
        return intervals.size()-Temp.size();
    }
};

2. 二维数组

2.1 旋转矩阵

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
给定 matrix = [
[1,2,3],
[4,5,6],
[7,8,9]],
原地旋转输入矩阵,使其变为:[
[7,4,1],
[8,5,2],
[9,6,3]]

2.1.1 C++:非原地旋转

class Solution {
     
public:
    void rotate(vector<vector<int>>& matrix) {
     
        int len = matrix.size();
        // 保存参考信息
        // 可用auto buff = matrix;
        vector<vector<int>> buff = matrix; 
        for(int i=0; i<len; i++){
     
            for(int j=0; j<len; j++){
     
            	// 利用参考信息改变原矩阵,枚举一行出规律
                matrix[i][j] = buff[len-1-j][i];
            }
        }
    }
};

2.2 零矩阵

2.2.1 C++(两次循环)

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
输入:[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]]
输出:[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]]

class Solution {
     
public:
    void setZeroes(vector<vector<int>>& matrix) {
     
        int x = matrix.size();
        if(x==0) return;
        int y = matrix[0].size();
        vector<int> x_buff(x, 0);
        vector<int> y_buff(y, 0);
        // 取样
        for(int i=0; i<x; i++){
     
            for(int j=0; j<y; j++){
     
                if(matrix[i][j] == 0){
     
                    x_buff[i] = 1;
                    y_buff[j] = 1;
                }
            }
        }
        // 刷新
        for(int i=0; i<x; i++){
     
            for(int j=0; j<y; j++){
     
                if(x_buff[i]==1 || y_buff[j]==1){
     
                    matrix[i][j] = 0;
                }
            }
        }
    }
};

2.3 ZIG-ZAG

C++

class Solution {
     
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& matrix) {
     
        vector<int> nums;
        // 获得行列数(下标)
        int m = matrix.size();
        // 针对[],[[]]的万能答案
        if(m==0) return {
     };
        int n = matrix[0].size();
        int i = 0;	// i 是 x + y 的和
        // 前提:对角线上x,y下标之和相同
        while (i < m + n)
        {
     
            // 第 (下标和为)1 3 5 ... 趟(i-j+)
            // 确定 x y 的初始值
            int x1 = (i < m) ? i : m - 1;	
            int y1 = i - x1;
            // 行不等0 且 列不到底
            while (x1 >= 0 && y1 < n)
            {
     
                nums.push_back(matrix[x1][y1]);
                x1--;
                y1++;
            }
            // 下一条对角线
            i++;
            // 没有下一条线了
            if (i >= m + n) break;
            // 第 (下标和为)2 4 6 ... 趟(j-i+)
           	// 确定 x y 的初始值
            int y2 = (i < n) ? i : n - 1;            
            int x2 = i - y2;
            // 列不等0,行不到底
            while (y2 >= 0 && x2 < m)
            {
     
                nums.push_back(matrix[x2][y2]);
                x2++;
                y2--;
            }
            i++;
        }
        return nums;
    }
};

3. 字符串

*子串,比较操作,连接操作

3.0 #中包含的字符串操作

赋值:

string s1 = "Hello World!";
string s2 = s1;
string s3(s1);

比较:

bool (s1==s3)
bool s1.compare(s3)

字符串是否可以改变:
可以直接赋值改变:C++
不可以直接赋值改变:Python,Java

3.1 最长公共前缀:.substr[op,ed)活用

编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。

3.1.1 横向扫描

时间复杂度
空间复杂度

class Solution {
     
public:
    string longestCommonPrefix(vector<string>& strs) {
     
        int len = strs.size();
        if(len == 0) return "";

        string prefix = strs[0];
        for(int i=1; i<len; i++){
     
            int cnt = min(prefix.size(), strs[i].size());
            int index = 0;
            while(index < cnt && prefix[index]==strs[i][index]){
     
                ++index;
            }
            prefix = prefix.substr(0, index);
        }
        return prefix;
    }
};

3.1.2 纵向扫描

时间复杂度
空间复杂度

class Solution {
     
public:
    string longestCommonPrefix(vector<string>& strs){
     
        int len = strs.size();
        if(len==0) return "";
        string prefix = strs[0];
        int cnt = prefix.size();
        for(int i=0; i<cnt; i++){
     
            for(int j=1; j<len; j++){
     
                if(i==strs[j].size()) return strs[j];
                if(prefix[i] != strs[j][i]) return prefix.substr(0,i);
            }
        }
        return prefix;
    }
};

3.1.3 分而治之

时间复杂度
空间复杂度

class Solution {
     
public:
    string longestCommonPrefix(const vector<string>& strs) {
     
        int len = strs.size();
        if(len==0) return "";
        int op = 0;
        int ed = len-1;
        // 分治入口
        return merge(strs, op, ed);
    }

    string merge(const vector<string>& strs, int op, int ed){
     
    	// 分治(叶子)出口
        if(op==ed) return strs[op];
		// 3种(混淆)取区间内中值
		// 1. 较慢
		// int mid = op + (ed-op)/2;
        // 2. 溢出错误做差后千万不要右移
        // int mid = op + (ed-op)>>1;
        // 3. 最快
        int mid = (op+ed)>>1;
        // 分治获得左右公共prefix
        string leftpub = merge(strs, op, mid);
        string rightpub = merge(strs, mid+1, ed);
        // 获得最终公共prefix
        return Compare(leftpub, rightpub);
    }
    
    string Compare(const string left, const string right){
     
        // 决定右值的上界
        int cnt = min(left.length(), right.size());
        // 判断右值的下界
        for(int i=0; i<cnt; i++){
     
            if(left[i]!=right[i]){
     
                return left.substr(0, i);
            }
        }
        return left.substr(0, cnt);
    }
};

3.2* 回文子串数量

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
输入:“abc” 输出:3
解释:三个回文子串: “a”, “b”, “c”

输入:“aaa” 输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:输入的字符串长度不会超过 1000 。

class Solution {
     
public:
    int countSubstrings(string s) {
     
        int len = s.length();
        // 除空字符串
        if(len==0) 
            return 0;
        // 初始化置0
        int dp[len][len];
        memset(dp, 0, sizeof(dp));
        // 对角线填1
        for(int i=0; i<len; i++)
            dp[i][i] = 1;
		int nums = len;
        // 动态规划填dp矩阵
        // 行为串首,列为串尾
        for(int j=1; j<len; j++){
     
            for(int i=0; i<j; i++){
     
            	// 子串长度大于2,掐头去尾是回文,头尾相等
                if(j>i+1 && dp[i+1][j-1]==1 && s[i]==s[j]){
     
                    dp[i][j] = 1;
                    nums++;
                }
                // 子串长度等于2,且回文
                if(j==i+1 && s[i]==s[j]){
     
                    dp[i][j] = 1;
                    nums++;
                }
            }
        }
        return nums;
    }
};

3.2 最长回文子串:

给定一个字符串 s,找到 s 中最长的回文子串。
你可以假设 s 的最大长度为 1000(潜台词:可用动态规划)。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:
输入: “cbbd”
输出: “bb”

3.2.1 中心扩散

3.2.2 动态规划

用到 < string > 库中的.substr(start, len);

class Solution {
     
public:
    string longestPalindrome(string s) {
     
        int len = s.size();
        if(len==0) return "";

        int dp[len][len];
        memset(dp, 0, sizeof(dp));

        for(int i=0; i<len; i++){
     
            dp[i][i] = 1;
        }
        // 设置初值
        int maxlen = 1;
        int begin = 0;

        for(int j=1; j<len; j++){
     
            for(int i=0; i<j; i++){
     
                // 当子串长为2时
                if(j==i+1 && s[i]==s[j]){
     
                    dp[i][j] = 1;
                    maxlen = max(maxlen, j-i+1);
                    if(maxlen == j-i+1) begin = i;
                }
                // 当子串长大于2时
                if(j>i+1 && dp[i+1][j-1]==1 && s[i]==s[j]){
     
                    dp[i][j] = 1;
                    maxlen = max(maxlen, j-i+1);
                    if(maxlen == j-i+1) begin = i;
                }
            }
        }
        return s.substr(begin, maxlen);
    }
};

3.3 翻转字符串中的单词

level 1:
输入: “the sky is blue”
输出: “blue is sky the”

level 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

level 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

3.3.1 双指示符辅助,外部翻转

时间复杂度:O(N)
空间复杂度:O(N)

class Solution {
     
public:
    string reverseWords(string s) {
     
        int len = s.length();
        if(len==0) return "";
		// 定义取单词指示符
        int L = len-1;
        int R = L;
        string rev = "";
        // 遍历字符串
        while(L>=0){
     
        	// 1.清除空字符:
        	// 因为在2后进行该判断会刷新LR,永久跳过3操作
            while(L>=0 && s[L]==' '){
     
                L--;
                R = L;
            }
            // 2.定位单词头部
            while(L>=0 && s[L]!=' '){
     
                L--;
            }
            // 3.取单词
            if(L!=R){
     
                rev += s.substr(L+1, R-L) + " ";
                L--;
                R = L;
            }
        }
        // 若原字符串不是“    ”形式,则消除3.最后一次操作出现多加一个空字符的bug
        if(!rev.empty()){
     
            rev.pop_back();
        }
        return rev;
    }
};

3.3.2 (感觉好麻烦…)

时间复杂度:O(N)
空间复杂度:O(1)

class Solution {
     
public:
    string reverseWords(string s) {
     
        // 反转整个字符串
        reverse(s.begin(), s.end());

        int n = s.size();
        int idx = 0;
        for (int start = 0; start < n; ++start) {
     
            if (s[start] != ' ') {
     
                // 填一个空白字符然后将idx移动到下一个单词的开头位置
                if (idx != 0) s[idx++] = ' ';

                // 循环遍历至单词的末尾
                int end = start;
                while (end < n && s[end] != ' ') s[idx++] = s[end++];

                // 反转整个单词
                reverse(s.begin() + idx - (end - start), s.begin() + idx);

                // 更新start,去找下一个单词
                start = end;
            }
        }
        s.erase(s.begin() + idx, s.end());
        return s;
    }
};

3.3.3 大佬方法:使用字符串流

1、用栈FILO

class Solution {
     
public:
    string reverseWords(string s) {
     
        stack<string> stk;
        string rev = "";
        string x;
        stringstream ss(s);
        while(ss >> x){
     
            stk.push(x);
        }
        while(!stk.empty()){
     
            rev += stk.top() + " ";
            stk.pop();
        }
        if(!rev.empty()){
     
            rev.pop_back();
        }
        return rev;
    }
};

2、不用栈字符串前置叠加

class Solution {
     
public:
    string reverseWords(string s) {
     
        string rev, x;
        stringstream ss(s);
        while(ss >> x){
     
            rev = x + " " + rev;
        }
        // 依旧需要去空格(因为开头rev为空)       
        return rev.substr(0, rev.size()-1);
    }
};

4. 双指针

4.1 翻转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,必须原地修改输入数组、使用 O(1)的额外空间解决这一问题。
假设数组中的所有字符都是 ASCII 码表中的可打印字符。
输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
输入:[“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

class Solution {
     
public:
    void reverseString(vector<char>& s) {
     
        int len = s.size();
        if(!len) return;
		// 声明字节指针变量:
        char *p1 = &s[0];
        char *p2 = &s[len-1];
        // 移动字节指针(p1,p2保存指向变量空间的地址)
        while(p1<p2){
     
        	// 字节指针变量*p1作为右值:所取指向的变量空间的值
            char temp = *p1;
            // 字节指针变量*p1作为左值:将右值入指向的变量空间
            *p1 = *p2;
            *p2 = temp;
            // 地址+-(仅适用于字节数组的地址)
            p1++;
            p2--;
        }
        return;
    }
};

4.2 给定有序数组,寻找两数之和=目标数

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

4.2.1 从头开始遍历左值并对右值进行二分查找

class Solution {
     
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
     
        for(int i=0; i<numbers.size(); i++){
     
        	// 固定左值
            int low = i+1;
            // 从终点开始对右值进行二分查找
            int high = numbers.size()-1;
            while(low<=high){
     
                int middle = low + (high-low)/2;
                if(numbers[middle] == target - numbers[i]){
     
                    return {
     i+1, middle+1};
                }else if(numbers[middle] > target - numbers[i]){
     
                    high = middle-1;
                }else{
     
                    low = middle+1;
                }
            }
        }
        // 无正确结果时的输出
        return {
     -1, -1};
    }
};

4.2.2 双端指针向内逼近

class Solution {
     
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
     
    	// 头尾设置指针
        int i = 0;
        int j = numbers.size()-1;
        while(i<j){
     
            if(numbers[i]+numbers[j]==target){
     
                return {
     i+1, j+1};
            }else if(numbers[i]+numbers[j]<target){
     
                i++;
            }else{
     
                j--;
            }
        }
        // 无正确结果时的输出
        return {
     -1,-1};
    }
};

4.3 快慢指针

4.3.1 移除数组中所有指定元素

有一个数组 nums 和一个值 val,请 原地移除所有数值等于val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。

class Solution {
     
public:
	// & 引用将改变所调用数组的布局
    int removeElement(vector<int>& nums, int val) {
     
        int slow=0;
        int fast=0;
        while(fast < nums.size()){
     
            // 当fast遇到指定值时:
            slow留在指定位置处不动
            fast向前移动一位
            // 当fast再次遇到非指定值时
            slow保存fast当前值,并前移一位
            fast向前移动一位
            if(nums[fast]!=val){
     
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        // 输出最终位数
        return slow;
    }
};

4.3.2 最大连续1的个数(单次循环即可)

给定一个二进制数组, 计算其中最大连续1的个数。
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
注意: 输入的数组只包含 0 和1。输入数组的长度是正整数,且不超过 10,000。

class Solution {
     
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
     
        int len = nums.size();
        int max1 = 0;
        int j = 0;
        int m = 0;
        while(j<len){
     
        	// 局部连续记录
            if(nums[j]==1)
                m++;
            // 局部长度记录与更新
            if(nums[j]!=1 && m!=0){
     
                max1 = max(m, max1);
                m = 0;
            }
            j++;
        }
		// 防止数组仅为1项且存在最大连续紧挨终点的情况
        return max(m, max1);
    }
};

4.3.3 长度最小的子数组

class Solution {
     
public:
    int minSubArrayLen(int s, vector<int>& nums) {
     
        int len = nums.size();
        if(!len) return 0;
        int i = 0;
        int j = 0;
        int res = 65535;
        int temp = 0;
        while(j<len){
     
        	// 向后更新
            temp += nums[j];
            // 局部和达到要求
            while(temp>=s){
     
            	// 局部求和并剔除头部
                temp -= nums[i];
                res = min(res, j-i+1);
                i++;
            }
            // 局部求和未达到要求,向后移动
            j++;
        }
        if(res==65535) return 0;
        return res;
    }
};

5. 队列

循环队列操作

private:
    vector<int> data;
    int head;
    int tail;
    int size;
public:
    /** Initialize your data structure here. Set the size of the queue to be k. */
    MyCircularQueue(int k) {
     
        data.resize(k);
        head = -1;
        tail = -1;
        size = k;
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    /** 入队 */
    bool enQueue(int value) {
     
        if (isFull()) {
     
            return false;
        }
        if (isEmpty()) {
     
            head = 0;
        }
        // 循环方式队尾向后移动一位并赋值
        tail = (tail + 1) % size;
        data[tail] = value;
        return true;
    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    /** 出队 */
    bool deQueue() {
     
        if (isEmpty()) {
     
            return false;
        }
        // 如果头尾相等,则等价于队列清零
        if (head == tail) {
     
            head = -1;
            tail = -1;
            return true;
        }
        // 队首向后移动一位
        head = (head + 1) % size;
        return true;
    }
    
    /** Get the front item from the queue. */
    int Front() {
     
        if (isEmpty()) {
     
            return -1;
        }
        return data[head];
    }
    
    /** Get the last item from the queue. */
    int Rear() {
     
        if (isEmpty()) {
     
            return -1;
        }
        return data[tail];
    }
    
    /** Checks whether the circular queue is empty or not. */
    bool isEmpty() {
     
    	// 队首为-1则为空
        return head == -1;
    }
    
    /** Checks whether the circular queue is full or not. */
    bool isFull() {
     
    	// 队尾向后移动一位遇到队首则为满
        return ((tail + 1) % size) == head;
    }

队列实用操作:
queue<> q
q.push(k)
q.pop()
q.front()
q.back()
q.size()
q.empty()

你可能感兴趣的:(基本功练习,算法,c++,数据结构)