LeetCode(1-200)

这篇博客详细介绍了LeetCode的前200道题目,涵盖了数组、字符串、链表、二叉树、回文、排序、搜索等多种算法问题,旨在帮助读者提升编程能力和算法理解。
摘要由CSDN通过智能技术生成

目录

LeetCode 1. 两数之和

/*
这道题保证一定有解,所以不需要特判数组为空的边界条件
1.暴力枚举,两重循环 O(n^2)
2.排序,双指针 O(nlogn)+O(n) 但是排序下标会改变 可以用pair存储数值和原下标
3.一次遍历 O(n):哈希表
每次枚举第二个数,查看当前枚举的元素的前面有没有对应的答案,要查询某个数是否存在,可以用哈希表,它可以在O(1)时间复杂度内找到某个数是否存在
这里哈希表用unordered_map,它的底层实现是哈希表,每步操作O(1)
map底层实现是平衡二叉树,每步操作O(logn)
*/
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> h;//首先定义一个哈希表,把数组中遍历到的每个数映射成对应的下标
        for(int i=0;i<nums.size();i++){
            //从前往后遍历数组中的每一个元素
            int r = target - nums[i];//要查询的数是target-nums[i]
            if(h.count(r)) return {h[r],i};//如果要查询的数在哈希表中,返回两者的下标
            //如果没有的话就把当前元素插到哈希表中
            h[nums[i]]=i;
        }
        //由于保证一定有解,所以前面一定会return,为了避免警告
        return {};
    }
};

LeetCode 2. 两数相加

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
/*
(链表模拟人工加法) O(n)

这是道模拟题,所给链表存储数是低位在前,高位在后,模拟我们小时候列竖式做加法的过程:

从最低位至最高位,逐位相加,如果和大于等于10,则保留个位数字,同时向前一位进1.
如果最高位有进位,则需在最前面补1.

做有关链表的题目,有个常用技巧:添加一个虚拟头结点:ListNode *dummy = new ListNode(-1);,可以简化边界情况的判断。
时间复杂度:由于总共扫描一遍,所以时间复杂度是 O(n)
*/
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //为了方便,先定义一个虚拟头结点,这样就不用特判头结点
        auto dummy = new ListNode(-1), cur = dummy;//cur表示当前这个和的尾节点
        int t=0;//进位
        while(l1||l2||t){
            //当l1没有循环完,或者l2没有循环完,或者进位t不为0时就一直做
            if(l1) t+=l1->val,l1=l1->next;
            if(l2) t+=l2->val,l2=l2->next;
            cur=cur->next=new ListNode(t%10);
            t/=10;
        }
        return dummy->next;
    }
};

LeetCode 3. 无重复字符的最长子串

/*
给定一个字符串,请找出最长的不包含重复字符的子串

请注意子串和子序列的区别:
子串一定是连续的一段
子序列不一定是连续的一段,但下标要求是递增的

双指针,滑动窗口算法
考虑如何枚举到所有情况,取一个max,所有子串可以根据尾节点分为n类
枚举以i为尾端点的所有子串,在其中找到以i为尾端点的最远的j使得[j,i]为无重复字符的最长子串
如果直接用哈希表,双重循环的话,时间复杂度为O(n^2)
双指针优化考虑有无单调性,在这道题中也就是尾端点i往后的话,对应的起始点j是否也会往后,
答案是肯定的,反证法可证明
用哈希表动态维护[j,i]这个区间每个字符出现的次数
*/
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //定义一个哈希表动态维护统计区间中每个字符出现的次数
        unordered_map<char, int> h;
        int res=0;
        for(int i=0,j=0;i<s.size();i++){
            //每次把当前遍历的字符加进去
            h[s[i]]++;
            //如果出现重复必然是刚加进去的s[i]导致的重复
            while(h[s[i]]>1) h[s[j++]]--;//把j往后移动一位
            res=max(res,i-j+1);//更新答案
        }
        return res;
    }
};

LeetCode 4. 寻找两个正序数组的中位数

//时间空间复杂度都是O(n+m)
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        //归并排序思路,创建合并数组nums,并指定长度为nums1和nums2长度之和
        vector<int> nums;
        int size1 = nums1.size(), size2 = nums2.size();
        int size = size1 + size2;
        //nums.resize(size);这一步如果不加,往nums数组放元素只能push_back

        //建立变量i1和i2分别表示遍历数组nums1和nums2的下标,i表示数组nums下标
        int i1 = 0, i2 = 0, i = 0;

        //循环直到i1和i2均遍历到数组末尾
        while (i1 < size1 || i2 < size2) {
            //nums1首元素大于nums2或i2已遍历到末尾
            if (i2 >= size2 || (i1 < size1 && nums1[i1] <= nums2[i2])) {  
                //nums[i++] = nums1[i1++];
                nums.push_back(nums1[i1++]);
            }
            //nums2首元素大于nums1或i1已遍历到末尾
            else {
                // nums[i++] = nums2[i2++];
                nums.push_back(nums2[i2++]);
            }
        }

        //根据长度奇偶情况决定返回单个数还是两个数的平均值
        //注意题目要求所求中位数为double型,求值和返回值时得先转换为double型
        //注意这里的-1是因为下标从0开始,比如size==4的话,是要求下标为1和2的平均值
        return size % 2 == 0 ? (nums[size / 2 - 1] + nums[size / 2]) / 2.0 : (double)nums[size/2];
    }
};
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
/* 
因为我们的最终目的是得到中位数即可,不必真正合并数组,只要得到目标元素即可,
因此可以改成用两个变量记录下求中位数所要的元素,时间复杂度任然线性,但相较于上个做法时间复杂度减半                
*/
        int size1 = nums1.size(), size2 = nums2.size();
        int size = size1 + size2;
        int val1,val2;

        int i1 = 0, i2 = 0, i = 0;
        while ((i1 < size1 || i2 < size2) && i <= size / 2) {
            if (i2 >= size2 || (i1 < size1 && nums1[i1] <= nums2[i2])) {
                if (i == size / 2 - 1) val2 = nums1[i1];
                else if (i == size / 2) val1 = nums1[i1];
                i++;
                i1++;
            }
            else {
                if (i == size / 2 - 1) val2 = nums2[i2];
                else if (i == size / 2) val1 = nums2[i2];
                i++;
                i2++;
            }
        }

        return size % 2 == 0 ? ((double)val1+ (double)val2) / 2 : (double)val1;
    }
};
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
/*
首先考虑:在两个有序数组中,找出第k小数,那么第 (n+m)/2小数就是我们要求的中位数
我们从nums1和nums2中各取前k/2个元素,如果nums1[k/2−1]>nums2[k/2−1],所以总体来看,两个数组中小于nums2[k/2−1]
的元素一定不足k个,所以我们可以把nums2中前k/2个数去掉,在剩下数中找第k/2小数,变成一个递归的子问题,每次要减少一半元素,最多递归logk次,所以时间复杂度是log(n+m)
*/
        int tot = nums1.size() + nums2.size();
        if (tot % 2 == 0) {//中位数要注意奇偶数,这里+1是因为要找第几个元素,size==4表示要找第2和第3个元素
            int left = find(nums1, 0, nums2, 0, tot / 2);
            int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (left + right) / 2.0;//一定注意/2.0,不然会下取整
        } else {
            return find(nums1, 0, nums2, 0, tot / 2 + 1);
        }
    }

    int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        //为了方便,假定第一个数组要查询的部分较短
        if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
        if (k == 1) {//第一个边界情况,求最小值,也注意较小数组要查询的部分可能为空
            if (nums1.size() == i) return nums2[j];
            else return min(nums1[i], nums2[j]);
        }//第二个边界,较小数组为空
        if (nums1.size() == i) return nums2[j + k - 1];
        int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
        //这里si是从i开始第k/2个元素的下一个位置,所以比较时候要-1,sj一样的道理
        if (nums1[si - 1] > nums2[sj - 1])
            return find(nums1, i, nums2, sj, k - (sj - j));
        else
            return find(nums1, si, nums2, j, k - (si - i));
    }
};
/*
(二分) O( log(min(n,m)) )
二分查找,为了方便讨论我们首先假设两个数组的长度分别为n,m,并且有n <= m,并且n + m是奇数,那么我们要找的数字其实就是两个数组合并后第k = (n + m + 1) / 2小的数字。我们尝试将两个数组划分成两个部分,A数组左侧有i个元素,B数组左侧有j个元素,并且i + j = k。
          left_part          |        right_part
    A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[n-1]
    B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[m-1]
如果我们能够保证A[i - 1] < B[j] && B[j - 1] < A[i],那么说明left_part中的所有元素都小于right_part中的元素,并且第k小的元素就是max(A[i - 1],B[j - 1])。
如果A[i - 1] > B[j]说明i偏大,我们需要将i缩小尝试得到A[i - 1] < B[j]。
如果B[j - 1] > A[i]说明i偏小,我们需要将i放大尝试得到B[j - 1] < A[i]。
那么我们使用二分查找来找到左边放多少个i数字比较合适,初始搜索区间为[0:n],(可以一个元素都不放,也可以全放)如果左边放置i个元素,那么右边放置j = k - i个元素。
接下来我们考虑一些边界条件:
如果i = 0,相当于最小的k个数都在B中,这时整体第k小的元素就是B[k - 1]
如果j = 0,相当于最小的k个数都在A中,这时整体第k小的元素就是A[k - 1]
否则,最小的k个数,i个在A中,j个在B中,这时整体第k小的元素就是max(A[i - 1],B[j - 1])
上面我们的讨论,我们是基于n + m是奇数的,这时候我们只需要找到上述元素就好了,但是当n + m是偶数的时候,我们还需要找到right_part中最小的元素,这个值也就是min(A[i],B[j]),这时候仍然需要讨论一些边界情况:
如果i = n,那么A中没有比A[i - 1]还大的了,那么只能是B[j]
如果j = m,那么B中没有比B[j - 1]还大的了,那么只能是A[i]
否则,整体第k + 1小的元素就是min(A[i],A[j])
*/
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(),m = nums2.size();
        if(n > m){
            swap(nums1,nums2);
            swap(n,m);
        }
        int l = 0,r = n,k = (n + m + 1) >> 1;
        while(l <= r)
        {
            int i = (l + r) >> 1,j = k - i;
            if(i < n && nums1[i] < nums2[j - 1] )
                l = i + 1;
            else if(i > 0 && nums1[i - 1] > nums2[j])
                r = i - 1;
            else
            {
                int max_left,min_right;
                if(i == 0) max_left = nums2[k - 1];
                else if(j == 0) max_left = nums1[k - 1];
                else max_left = max(nums1[i - 1],nums2[j - 1]);
                if((n + m) % 2 == 1) return max_left;

                if(i == n) min_right = nums2[j];
                else if(j == m) min_right = nums1[i];
                else min_right = min(nums1[i],nums2[j]);
                return (max_left + min_right)/2.0;
            }
        }
        return 0.0;
    }  
};

LeetCode 5. 最长回文子串

/*
1.双指针 时间复杂度是O(n^2)
回文串,左右对称,根据回文串长度奇偶性分为两种
奇数:满足左右两边两两对称即可,中间元素无所谓
偶数:两两对称相等
先枚举每个回文串的中心点,当中心点确定之后,用两根指针同时从中心开始往两边走,直到两个字符不一样,或者某根指针走到边界为止,这样就找到了以当前点为中心的最长回文子串 此时回文串是[l+1,r-1] 对应长度是(r-1)-(l+1)+1
如果是长度为奇数的回文串,l和r初始化为l=i-1 r=i+1
如果是长度为偶数的回文串,l和r初始化为l=i r=i+1
首先枚举中心点,遍历一次,时间复杂度为O(n),中心点确定之后,枚举两根指针是O(n),总共时间复杂度是O(n^2)
另外两种方法,第一种字符串哈希+二分,时间复杂度较低,但比较难,第二种DP时间复杂度一样,但需要开额外数组,空间复杂度较高
*/
class Solution {
public:
    string longestPalindrome(string s) {
        string res;
        for(int i=0;i<s.size();i++){//遍历每一个字符,探索以它为中心的最长回文子串
            //先探索奇数情况
            int l=i-1,r=i+1;
            //当两根指针未越界,并且所指字符相同时,各自向外走一步
            while(l>=0&&r<s.size()&&s[l]==s[r]) l--,r++;
            //跳出循环时,[l+1,r-1]这一段字符串是i所对应的最长奇数回文子串
            if(res.size()<r-l-1) res=s.substr(l+1,r-l-1);
            l=i,r=i+1;
            while(l>=0&&r<s.size()&&s[l]==s[r]) l--,r++;
            if(res.size()<r-l-1) res=s.substr(l+1,r-l-1);
        }
        return res;
    }
};
/*
2.区间DP 时间复杂度和空间复杂度都是O(n^2)
*/
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        bool dp[1001][1001];//dp[i][j]表示(i,j)是否为回文子串
        string ans;
        for(int len = 0; len < n; ++len){//len表示j与i相差几个元素
            for(int i = 0; i+len < n; ++i){
                int j = i + len;
                dp[i][j] = s[i]==s[j];
                if(len > 1)//len==0或1表示区间只有一个或两个元素,严格大于1表示区间至少有三个元素
                    dp[i][j] = dp[i+1][j-1] && dp[i][j];
                if(dp[i][j] && len+1 > ans.size())
                    ans = s.substr(i,len+1);
            }
        }
        return ans;
    }
};
/*
左右两半边子串相等,左边子串正序哈希值和右边子串倒序哈希值相等
回文串两大类,长度分为奇数和偶数
先看奇数类,枚举中心点,二分求出当前中心点的最大半径
对原来字符串变形,使得所有回文串长度都是奇数,在每两个字母之间加上一个没出现的字符即可
*/
#include <iostream>
#include <string.h>
using namespace std;

typedef unsigned long long ULL;
const int N = 2000010, base = 131;

char str[N];
ULL hl[N], hr[N], p[N];//正序逆序所有字符串的哈希值

ULL get(ULL h[], int l, int r)
{//求子串哈希值
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    int T = 1;
    while (scanf("%s", str + 1), strcmp(str + 1, "END"))
    {
        int n = strlen(str + 1);
        for (int i = n * 2; i; i -= 2)
        {
            str[i] = str[i / 2];
            str[i - 1] = 'a' + 26;
        }
        n *= 2;
        p[0] = 1;
        for (int i = 1, j = n; i <= n; i ++, j -- )
        {//预处理正序,逆序所有前缀哈希值和次方数组
            hl[i] = hl[i - 1] * base + str[i] ; 
            hr[i] = hr[i - 1] * base + str[j] ;
            p[i] = p[i - 1] * base;
        }

        int res = 0, k=0;
        //char str_res[N];
        for (int i = 1; i <= n; i ++ )
        {//枚举中点,二分半径
            int l = 0, r = min(i - 1, n - i);
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
//下标,正序第i - mid到第i - 1个,倒数第 n - (i + mid) + 1到第n - (i + 1) + 1)个
                if (get(hl, i - mid, i - 1) == get(hr, n - (i + mid) + 1, n - (i + 1) + 1)) l = mid;
                else r= mid-1;
            }
//这样求出来是包括*的长度 整个回文子串要么是*比字母多一个,要么是字母比*多一个
            if (str[i - l] <= 'z') res = max(res, l + 1);//如果边界是字母,说明字母多一个
            else res = max(res, l);            

            // if (str[i - l] <= 'z') {
            //     if (l+1>res) res=l+1,k=i;
            // }
            // else{
            //     if (l>res) res=l,k=i;
            // }
        }

        // for(int j=k-res, m=0;j<=k+res;j++){
        //     if(str[j]<='z') str_res[m++]=str[j];
        // }

        //cout<<str_res<<endl;

        printf("Case %d: %d\n", T ++ , res);
    }

    return 0;
}
class Solution {
public:
/*
先进行正向和反向hash
假设中点在 mid,臂长为l
如果当前mid-l的哈希值不等于mid+l的哈希值
说明当前中点到两边的回文长度小于前面的 直接continue进行下一个
如果哈希值相等 则判断l+1之后的两字符是否相等,如果相等,说明l+1后还为回文串继续判断l+1直到不满足
储存更新的 mid和l
输出
*/
    typedef unsigned long long ULL;
    int t=0,P=131;
    char a[3000];
    ULL hl[3000],hr[3000],p[3000];

    string longestPalindrome(string s) {
        string res;
        int ans=0,mid=0,l;
        a[++t]='#';
        for(int i=0;i<s.size();i++)
        {//对原来字符串变形,使得所有回文串长度都是奇数
            a[++t]=s[i];
            a[++t]='#';
        }
        a[t+1]='\0';
        p[0]=1;

        for (int i = 1, j = t; i <= t; i ++, j -- )
        {//预处理正序,逆序所有前缀哈希值和次方数组
            hl[i]=hl[i-1]*P+a[i];
            hr[i]=hr[i-1]*P+a[j] ;
            p[i]=p[i-1]*P;
        }

        for(int i=1;i<=t;i++)
        {//枚举中点,探索半径
            l=ans;//ans是遍历前面字符所找到的最长回文子串的长度,探索在这个基础上能否变长
            if(i+l>t||i-l<1) continue;//说明遍历到最后一小部分,不用继续判断了

            //下标,正序第i - l到第i - 1个,倒数第 n - (i + mid) + 1到第n - (i + 1) + 1)个
            if(hl[i-1]-hl[i-l-1]*p[l]!=hr[t-i]-hr[t-i-l]*p[l]) continue;
            while(i+l+1<=t&&i-l-1>=1&&a[i+l+1]==a[i-l-1]) l++;
            
            if(l>=ans)
            {
                ans=l;
                mid=i;
            }
        }
        for(int i=mid-ans;i<=mid+ans&&mid+ans<=t;i++)
        {
            if(a[i]!='#')
                res+=a[i];
        }
        return res;

    }
};

LeetCode 6. Z 字形变换

/*
找规律,第一行是以0(下标)开头的,公差是2n-2的等差数列;中间的行可以拆成两个等差数列,分为在竖线上的数和在斜线上的数,最后一行也是一个等差数列,所有等差数列的公差都是2n-2
竖线上的开头元素是0到n-1,斜线上的开头是2n-2-竖线上的开头
*/
class Solution {
public:
    string convert(string s, int n) {
        string res;//定义答案序列
        if (n == 1) return s;//边界条件判断,因为如果不判断的话,在n==1的情况下,公差算出来为0,会死循环
        for (int i = 0; i < n; i ++ ) {//枚举每一行
            if (i == 0 || i == n - 1) {//第一行或最后一行只有一个等差数列
                for (int j = i; j < s.size(); j += 2 * n - 2)
                    res += s[j];
            } 
            else {
                for (int j = i, k = 2 * n - 2 - i; j < s.size() || k < s.size(); j += 2 * n - 2, k += 2 * n - 2) {//两个等差数列,每一行两个等差数列的首项分别是i和2n-2-i
                    if (j < s.size()) res += s[j];
                    if (k < s.size()) res += s[k];
                }
            }
        }

        return res;
    }
};

LeetCode 7. 整数反转

/*
1.转换成字符串,反转之后变成整数
2.数学做法
先看怎么把数的每一位抠出来,以正数为例, x%10得到x的最后一位,x/10去掉最后一位
注意数学中取模之后,得到的数大于等于0,c++中对正数取模得正数,负数取模得负数,这样之后,这个代码也可以适用于负数
*/
class Solution {
public:
    int reverse(int x) {
//先用longlong来写,最后改成int
        long long r=0;
        while(x){
            r=r*10+x%10;//不用单独考虑正负情况,因为cpp取模的性质
            x/=10;
        }
        if(r>INT_MAX) return 0;
        if(r<INT_MIN) return 0;
        return r;
    }
};
class Solution {
public:
    int reverse(int x) {
        int r=0;
        while(x){//先试探一步,可能和三数之和这道题无关,但试探这个思想很像
            if(r>0&&r>(INT_MAX-x%10)/10) return 0;
            if(r<0&&r<(INT_MIN-x%10)/10) return 0;
            r=r*10+x%10;
            x/=10;
        }
        return r;
    }
};

LeetCode 8. 字符串转换整数 (atoi)

class Solution {
public:
    int myAtoi(string str) {
        int k = 0;
        while (k < str.size() && str[k] == ' ') k ++ ;//先把前面的空格删掉
        if (k == str.size()) return 0;//如果整个字符串都是空格的话,返回0

        int minus = 1;//对可能出现的正负符号处理
        if (str[k] == '-') minus = -1, k ++ ;
        else if (str[k] == '+') k ++ ;
//先用longlong来存,最后再改成int
        // long long res=0;
        // while(k<str.size()&&str[k]>='0'&&str[k]<='9'){//k指向数字字符
        //     res=res*10+str[k]-'0';//字符转换为数字
        //     k++;
        //     if(res>INT_MAX) break;//此时的res必然为正数,只是输出的时候考虑要不要加符号
        // }

        // res*=minus;
        // if(res>INT_MAX) return INT_MAX;
        // if(res<INT_MIN) return INT_MIN;
        // return res;
//longlong改成int,就是加一些比较麻烦的特判,也就是判断res=res*10+str[k]-'0'这一步操作之后是否溢出,分为正负特判
        int res = 0;
        while (k < str.size() && str[k] >= '0' && str[k] <= '9') {
            int x = str[k] - '0';
//正数res * 10 + x>INT_MAX前者会溢出,转化为res > (INT_MAX - x) / 10
//负数-res * 10 - x <INT_MIN,转化成-res < (INT_MIN + x) / 10
            if (minus > 0 && res > (INT_MAX - x) / 10) return INT_MAX;
            if (minus < 0 && -res < (INT_MIN + x) / 10) return INT_MIN;
//注意负数绝对值比正数绝对值多1,使用特判负无穷,注意这个只能单独判断,绝对不能在上面改成小于等于,因为这样的话像
//"-2147483647"也会输出-2147483648
            if (-res * 10 - x == INT_MIN) return INT_MIN;
            res = res * 10 + x;
            k ++ ;
        }
        res *= minus;
        return res;
    }
};

LeetCode 9. 回文数

//数值方法,类似于LeetCode 7. 整数反转,而且这道题并没有long long限制
class Solution {
public:
    bool isPalindrome(int x) {
        if(x<0||x&&x%10==0) return false;//如果x本身是负数,或者说x非0但是它最后一位是0的话,直接return
        int y=x;//先把所给整数存下来
        long long res=0;
        //从个位开始把x的每一位抠出来放前面
        while(x){
            res=res*10+x%10;
            x/=10;
        }
        return y==res;
    }
};
class Solution {
public:
    bool isPalindrome(int x) {
//数值方法,有longlong限制,只能用int,可以只翻转后一半,再判断是否和前一半相等即可
        if (x < 0 || x && x % 10 == 0) return false;
        int s = 0;
        while (s <= x)
        {
            s = s * 10 + x % 10;
            if (s == x || s == x / 10) return true; // 分别处理整数长度是奇数或者偶数的情况
            x /= 10;
        }
        return false;
    }
};
class Solution {
public:
    bool isPalindrome(int x) {
//字符串方法,转换成字符串
        if (x < 0) return 0;
        string s = to_string(x);
        return s == string(s.rbegin(), s.rend());
    }
};

LeetCode 10. 正则表达式匹配

/*
类似dp问题很多,比如给两个字符串,求最长公共子序列,能否匹配
正则表达式匹配,'.' 匹配任意单个字符,'*' 表示它前面的那一个字符可以出现任意多次(包括0次)
动态规划(序列模型):
状态表示 f[i,j]
    集合:所有s[1-i]和p[1-j]的匹配方案
    属性:bool 是否存在合法方案
状态计算
    如果p[j]不是'*',先看s[i]和p[j]是否匹配,两种情况:s[i]==p[j]或者p[j]=='.',并且f[i-1,j-1]也匹配
    如果p[j]是'*',枚举一下,这个'*'表示多少个字符,如果是0个字符f[i,j-2],1个字符f[i-1,j-2]&&s[i]匹配
    2个字符f[i-2,j-2]&&s[i]匹配&&s[i-1]匹配...
    f[i,j]  =f[i,j-2]   | f[i-1,j-2]&s[i]   | f[i-2,j-2]&s[i]&s[i-1]...
    f[i-1,j]=f[i-1,j-2] | f[i-2,j-2]&s[i-1] | f[i-3,j-2]s[i-1]&s[i-2]...
    上面一个式子: f[i,j]  =f[i,j-2] |  f[i-1,j] & s[i]匹配 优化和完全背包很像
*/
class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;//下标都从1开始,所以前面补上一个空格
        vector<vector<bool>> f(n + 1, vector<bool>(m + 1));//布尔数组
        f[0][0] = true;//初始化
        for (int i = 0; i <= n; i ++ )//它可以从0开始,因为没有字符的时候也可能匹配
            for (int j = 1; j <= m; j ++ ) {
//如果j==0的话,因为f[0][0]已经初始化过了,其他i不为0的情况一定不匹配,所以j从0开始没有意义
            //*和前面一个字符看作一个整体,如果遇到类似a*的a的话要跳过a
                if (j + 1 <= m && p[j + 1] == '*') continue;
                if ( p[j] != '*') {//如果i指向某个非空字符,并且p[j]!='*',i从1开始,否则i-1没有意义
                    f[i][j] = i &&f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.');
                } else if (p[j] == '*') {
                    f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');
                }
            }

        return f[n][m];
    }
};

LeetCode 11. 盛最多水的容器

/*
给定一个数组,数组中有n个非负整数,表示二维平面的n条直线的高度,找到其中的两条直线使得它们与 x 轴共同构成的容器可以容纳最多的水
(贪心 + 双指针) O(n)
储水容量取决于边界的间距以及两边的短板高度;
移动较高的边界,储水容量一定会减少,反之则未必,所以可以贪心的移动较短的边界,并不断更新答案;
利用反证法也可以严格证明这样操作的答案必然是最优解。
证明:假设左边指针先到达最优解的一边,要证明的是右边指针会一直向前移动到最优解的右边,反证法,假设某个时刻右边高度大于等于左边,右指针就不会往前移动,此时对应的面积一定大于最优解,矛盾
*/
class Solution {
public:
    int maxArea(vector<int>& height) {
        if (height.empty()) return 0;
        //res要取最大值,初始化一般为0或者负无穷,因为题目中均为非负整数,且求的是面积,初始化为0即可
        int i = 0, j = height.size() - 1, res = 0;
        while (i < j){
            res = max(res, (j - i) * min(height[i], height[j]));//每次是先更新一下最大值,再移动
            if (height[i] <= height[j]) i++;
            else j--;
        }

        return res;
    }
};

LeetCode 12. 整数转罗马数字

/*
基本字符	I	V	X	L	C	D	M
阿拉伯数字	1	5	10	50	100	500	1000
1.相同的数字连写,所表示的数等于这些数字相加得到的数,如:III=3;
2.小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数,如:VIII=8, XII=12;
3.小的数字在大的数字的左边(限于 IV、IX、XL、XC、CD和CM),所表示的数等于大数减小数得到的数,如:IV=4, IX=9;
4.正常使用时,连写的数字重复不得超过三次;
将所有减法操作看做一个整体,当成一种新的单位。从大到小整理所有单位得到:
M	CM	D	CD	C	XC	L	XL	X	IX	V	IV	I
1000	900	500	400	100	90	50	40	10	9	5	4	1
此时我们可以将目标整数看成这些单位值的加和,且同一种单位不能使用超过3次。所以我们尽可能优先使用值较大的单位
时间复杂度分析:计算量与最终罗马数字的长度成正比,对于每一位阿拉伯数字,罗马数字最多用4个字母表示(比如VIII=8),所以罗马数字的长度和阿拉伯数字的长度是一个数量级的,而阿拉伯数字的长度是O(logn),因此时间复杂度是 O(logn),时间复杂度和位数成正比,一个整数的位数是O(logn)级别。
*/
class Solution {
public:
    string intToRoman(int num) {
        int values[] = {
            1000,
            900, 500, 400, 100,
            90, 50, 40, 10,
            9, 5, 4, 1
        };
        string reps[] = {
            "M",
            "CM", "D", "CD", "C",
            "XC", "L", "XL", "X",
            "IX", "V", "IV", "I",
        };

        string res;
        for (int i = 0; i < 13; i ++ ) {
            while (num >= values[i]) {
                num -= values[i];
                res += reps[i];
            }
        }

        return res;
    }
};

LeetCode 13. 罗马数字转整数

class Solution {
public:
    int romanToInt(string s) {
        unordered_map<char, int> hash;
        hash['I'] = 1, hash['V'] = 5;
        hash['X'] = 10, hash['L'] = 50;
        hash['C'] = 100, hash['D'] = 500;
        hash['M'] = 1000;

        int res = 0;
        for (int i = 0; i < s.size(); i ++ ) {
            if (i + 1 < s.size() && hash[s[i]] < hash[s[i + 1]])
                res -= hash[s[i]];
            else
                res += hash[s[i]];
        }

        return res;
    }
};

LeetCode 14. 最长公共前缀

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
/*
两重循环暴力搜索 O(nm)
*/
        string res;
        if (strs.empty()) return res;//边界条件,判断是否为空

        for (int i = 0;i < strs[0].size(); i ++ ) {//枚举第i个字母是否完全一致
            char c = strs[0][i];
            for (auto& str: strs)
                if (str.size() <= i || str[i] != c)
                    return res;
            res += c;
        }

        return res;
    }
};
class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        //字符串数组为空则置res为空,否则置为第一个字符串
        string res = strs.empty() ? "" : strs[0]; 
        //遍历字符串数组
        for (string s : strs)
        {
            /*
            在字符串s中查找res并返回首字母的位置(find函数)
            如果首地址不为零,每次令res-1以缩短公共前缀
            比如说再flow中查找flower,没有找到,返回值为迭代器结尾(非0)
            公共前缀会减掉最后一个字母,为flowe。继续循环直到为flow

            如果是首字母不一样则公共前缀会清空
            */ 
            while (s.find(res) != 0) 
            {
                res = res.substr(0, res.length() - 1);
            }
        }
        return res;
    }
};

LeetCode 15. 三数之和

/*
这道题与后面的LeetCode 16. 最接近的三数之和 LeetCode 18. 四数之和是一类题
这种问题一般来说最优做法是双指针,次优是哈希表,这两者时间复杂度是一样的,但后者会多用一些空间
先不考虑是否重复的问题,首先双指针做法一定要有序,这是双指针的核心,所以要先将数组排序,先去枚举第一个数
为了避免重复,要求第一个指针指向的数最小,i<j<k 这样就会减少一些重复的情况,当i固定之后,j和k就可以用双指针了
时间复杂度 O(n^2)
*/
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;//首先定义一下答案
        sort(nums.begin(),nums.end());//这道题要用双指针做法,所以先排个序
        for(int i=0;i<nums.size();i++){//依次枚举三根指针
//如果i不是第一个数并且它所指向的元素与前面那个元素相同的话,直接continue,去重          
            if(i>0&&nums[i]==nums[i-1]) continue;
            for(int j=i+1,k=nums.size()-1;j<k;j++){//然后再双指针
                if(j>i+1&&nums[j]==nums[j-1]) continue;//同理跳过
/*这里我们是要找到三数相加大于等于0的最小的数,所以在ij固定的情况下,每次试探一下nums[k]的前面一个数nums[k-1]是否满足,满足的话k就往前面进一步*/
                while(j<k-1&&nums[i]+nums[j]+nums[k-1]>=0) k--;
                if(nums[i]+nums[j]+nums[k]==0){
                    res.push_back({nums[i],nums[j],nums[k]});
                }
            }
        }
        return res;
    }
};

LeetCode 16. 最接近的三数之和

/*
注意这道题是最接近,所以可能是小于target的最大值,也可能是大于target的最小值,所有左右两边情况都要考虑到,这道题保证答案只有一个,所以不用去判重,最坏情况是三重循环
优化和上一题一样,还是排序之后用双指针算法,先枚举i,i固定之后,枚举j和k,还是和刚才一样,对于每个j找到一个最小的k使得三者之和>=target,这样就可以枚举出来所有大于等于target的最小值
还要找另外一种情况,这里没必要特意找,只要能找出来大于等于target的最小的k,如果k可以-1的话,那加上nums[k-1]之后必然是小于target的最大值
*/
class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
/*因为任意三数之和都可能会是答案,而这些答案都与target有一个差值,最终是要找到差值最小的那个答案,所以最开始定义res的时候,用pair定义,后面那个数是可能的答案,前面那个是这个可能的答案与target的差值,放前面是因为min默认对pair前面那个数比较求最小,而且是要求最小值,所以初始化为INT_MAX*/
        pair<int, int> res(INT_MAX, INT_MAX);
        sort(nums.begin(),nums.end());//双指针,先排序
        for(int i=0;i<nums.size();i++){
            //这里不需要判重,所以不用像三数之和那道题一样多一步
            for(int j=i+1,k=nums.size()-1;j<k;j++){//双指针
//对于此时ij固定的情况,找一个可能的k,如果k可以往前走一步的话,k就往前走一步,加上之后看是否大于等于target
                while(k-1>j&&nums[i]+nums[j]+nums[k-1]>=target) k--;
    /*跳出循环要么是k不能往前走了,要么是k再往前走一步之后,三数相加必然小于target,至于加上nums[k]之后是否一定>=target,这个是不确定的*/
                //首先先求第一种情况,也就是大于等于target的最小的三数之和
                int s=nums[i]+nums[j]+nums[k];
                res=min(res, make_pair(abs(s-target),s));//注意这里为什么一定要加绝对值
                if(k-1>j){
                    s=nums[i]+nums[j]+nums[k-1];
                    res=min(res, make_pair(target-s,s));//此时target一定大于s
                }
            }
        }
        return res.second;

    }
};

LeetCode 17. 电话号码的字母组合

class Solution {
public:
/*
这道题是非常经典的dfs问题,dfs问题的话考虑清楚还是简单的,画一个递归搜索树即可,而且也不用回溯,遍历到某一条路径的终点的时候,直接把结果存下来就行,也不用干其他的事情
在递归的时候需要存下来方案或者说路径是什么,string path; 还需要存下来当前是第几位int u
这个做法时间复杂度和长度有关,最坏情况下每个数字有四种选择,总的就是4^n,外加push_back需要O(n)的时间复杂度
所有总的时间复杂度是n*4^n
*/
    vector<string> ans;
    string strs[10] = {
        "", "", "abc", "def",
        "ghi", "jkl", "mno",
        "pqrs", "tuv", "wxyz",
    };//每个数字可能的情况

    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return ans;
        dfs(digits, 0, "");//当前遍历第几位,最开始的路径
        return ans;
    }

    void dfs(string& digits, int u, string path) {//path是dfs当前的路径
        if (u == digits.size()) ans.push_back(path);//遍历到最后一位,在答案当中加入当前的方案
        else {
            for (auto c : strs[digits[u] - '0'])//u的下标从0开始,但strs里面的下标,数字2就是对于2
                dfs(digits, u + 1, path + c);
        }
    }
};

LeetCode 18. 四数之和

//双指针
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for (int i = 0; i < nums.size(); i ++ ) {
            if (i && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.size(); j ++ ) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                for (int k = j + 1, u = nums.size() - 1; k < u; k ++ ) {
                    if (k > j + 1 && nums[k] == nums[k - 1]) continue;
                    while (u - 1 > k && nums[i] + nums[j] + nums[k] + nums[u - 1] >= target) u -- ;
                    if (nums[i] + nums[j] + nums[k] + nums[u] == target) {
                        res.push_back({nums[i], nums[j], nums[k], nums[u]});
                    }
                }
            }
        }
        return res;
    }
};
/*
由最开始的二数之和,到三数之和,到现在的四数之和,虽然都可以用双指针,但如果扩张到n数之和呢,可以尝试dfs,又因为要不能有重复元素,所以要么哈希表,要么排序,这里我就直接sort排个序
深搜的搜索顺序,依次探索每个位置可以放哪些元素,如果放这个元素的话,下一个位置怎么放合适
另外这道题深搜必须加上剪枝,否则一定会超时
1.如果数组中剩余可选的数字数量少于待找数量n,则剪掉
2.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组中当前数字的下一个数字 > target,则说明后面的数无论怎么选,加起来都一定大于target,所以剪掉(递归返回)
3.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组最后一个数字 < target,则说明后面的数无论怎么选,加起来都一定小于target,所以剪掉(进行下一次循环)
*/
class Solution 
{
private:
    vector<vector<int>> ans;
    vector<int> myNums, subans;
    int tar, numSize;//这四个变量除了subans之外,其他定义成全局变量是为了dfs时也可以使用,否则只能在dfs时传入这些参数
    void DFS(int low, int sum)//前者是探索第low个位置可以放哪些数(从0开始),sum是已经选了的数的和
    {
        if (sum == tar && subans.size() == 4) {
            ans.push_back(subans);
            return;
        }
        for (int i = low; i < numSize; ++i) {
            if (i > low && myNums[i] == myNums[i - 1]) //去重
                continue; 
            if (numSize - i < int(4 - subans.size())) //剪枝
                return;
            if (i < numSize - 1 && sum + myNums[i] + int(3 - subans.size()) * myNums[i + 1] > tar) //剪枝
                return;
            if (i < numSize - 1 && sum + myNums[i] + int(3 - subans.size()) * myNums[numSize - 1] < tar) //剪枝
                continue;
            subans.push_back(myNums[i]);
            DFS(i + 1, myNums[i] + sum);
            subans.pop_back();
        }
        return;
    }
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        sort(nums.begin(), nums.end());
        myNums = nums;
        tar = target;
        numSize = nums.size();
        // if (numSize < 4)
        //     return ans;
        DFS(0, 0);
        return ans;    
    }
};

LeetCode 19. 删除链表的倒数第N个节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int k) {
//两次循环
        auto dummy = new ListNode(-1);//常用技巧,凡是头结点可能改变,建一个虚拟头结点
        dummy->next = head;

        int n = 0;//一次遍历找到链表总长度
        for (auto p = dummy; p; p = p->next) n ++ ;

        auto p = dummy;
        //遍历到倒数第k个点,从dummy开始,走1步,到达倒数第n-1个点,走n-k-1步,到达倒数第k+1个点
        for (int i = 0; i < n - k - 1; i ++ ) p = p->next;
        p->next = p->next->next;

        return dummy->next;   
    }
};
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int k) {
//一次循环
        auto dummy = new ListNode(-1);  // 首先新建一个链表的结点
        dummy->next = head;                     //令这个结点指向head
        auto first = dummy, second = dummy;   //同时设置双指针指向该节点
        for (int i = 0; i < k; i ++ )   
        //将first指针向前走k步,second 指针不动,还是在最前面,此时first和second这个节点相差k
        {
            first = first->next;
        }

            while (first -> next)
        {
//始终保持两个指针之间间隔n个结点,在first到达终点时,second的下一个结点就是从结尾数第n个结点
            first = first->next;
            second = second->next;
        }
        //直接删掉这个倒数第n个结点
        second->next = second->next->next;
        return dummy->next; 
    }
};

LeetCode 20. 有效的括号

class Solution {
public:
    bool isValid(string s) {
/*
非常经典的括号序列问题,这道题是很简单的栈,但括号序列会有很多衍生的问题,可以考dfs,也可以考dp,也可以考数据结构,也可以什么卡特函数相关
*/
        stack<char> stk;

        for (auto c : s) {
            if (c == '(' || c == '[' || c == '{') stk.push(c);
            else {
                if (stk.size() && abs(stk.top() - c) <= 2) stk.pop();
                else return false;
            }
        }

        return stk.empty();
    }
};

LeetCode 21. 合并两个有序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        /*
        二路归并算法,双指针
        */
        auto dummy = new ListNode(-1), tail = dummy;
        //凡是需要特判头结点的做法,建立一个头结点dummy node,每次都是在最后加节点,定义尾部tail
        while (l1 && l2) {
            if (l1->val < l2->val) {
                tail = tail->next = l1;
                l1 = l1->next;
            } else {
                tail = tail->next = l2;
                l2 = l2->next;
            }
        }

        if (l1) tail->next = l1;
        if (l2) tail->next = l2;
        return dummy->next;      
    }
};

LeetCode 22. 括号生成

在这里插入图片描述

class Solution {
public:
    vector<string> ans;//记录答案
/*
(暴力枚举,合法性判断) O(n*2^(2n))
生成所有的括号序列,然后用栈逐个检查合法性
*/
/*
这道题也是一个括号序列问题,给n对小括号,输出所有合法的括号序列方案,合法只要保证每一个左括号都有一个右括号对应即可
括号序列问题的推论,n对小括号什么情况下是合法的:
1.任意前缀中,左括号数量大于等于右括号数量
2.左右括号数量相等,这道题中这个条件一定满足
如果只是求数量的话有一个公式可以直接求,卡特兰数
每个位置左括号只有有一定可以填,右括号除了有之外,还需要满足之前的左括号数量严格大于右括号数量
*/
    vector<string> generateParenthesis(int n) {
        dfs(n, 0, 0, "");//最开始左右括号数量为0,当前序列什么都没有
        return ans;
    }

    void dfs(int n, int lc, int rc, string seq) {
        if (lc == n && rc == n) ans.push_back(seq);
        else {
            if (lc < n) dfs(n, lc + 1, rc, seq + '(');
            if (rc < n && lc > rc) dfs(n, lc, rc + 1, seq + ')');//严格大于才能保证放一个右括号之后大于等于
        }
    }
};

LeetCode 23. 合并K个排序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
/*
这道题比较直观的想法就是和合并两个有序链表类似,每次把k个序列最小的拿出来放到新序列的最后
找到k个中的最小值可用用堆,找到最小值时间复杂度是O(1),找到之后把最小值指针后移,再插入堆中,时间复杂度是
O(logn) 所以总的时间复杂度是O(nlogk)
这里有一个注意的地方,就是要给优先队列传入一个自定义的比较函数
*/
    struct Cmp {
        bool operator() (ListNode* a, ListNode* b) {//重载一下括号
            return a->val > b->val;//优先队列是大根堆,会把大的放前面,但我们要把小的放前面
            //所以翻转一下,写成大于号
        }
    };

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //传入自定义比较函数,不然会按照地址比较,但是我们要按照值来比较,所以要加额外两个参数
        //优先队列,容器的比较函数要传的是一个结构体
        priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;
        auto dummy = new ListNode(-1), tail = dummy;
        //先把所有非空的头结点插入堆中
        for (auto l : lists) if (l) heap.push(l);

        while (heap.size()) {
            auto t = heap.top();
            heap.pop();

            tail = tail->next = t;
            if (t->next) heap.push(t->next);
        }

        return dummy->next;
    }
};
/*
(二分治合并) O(nlogk)
类似于归并排序
将所有待合并的有序单向链表进行递归分治处理,即将当前链表的序列分成两部分,每部分递归进行合并,然后将当前左右两部分合并的结果再进行 二合并 即可。
递归每层的时间复杂度是整个结点个数O(n),由于每次是二分,所有总共有O(logk) 层。
故总时间复杂度为 O(nlogk)
*/
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
     ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();
        if(n == 0) return NULL;
        if(n==1) return lists[0];
        return merge(lists,0,n  - 1);

    }
    ListNode* merge(vector<ListNode*>& lists,int begin,int end)
    {
        if(begin == end)
            return lists[begin];
        else{
            int mid = (begin + end) /2;
            ListNode* l1 = merge(lists,begin,mid);
            ListNode* l2 = merge(lists,mid+1,end);
            ListNode* dummy = new ListNode(0);
            ListNode* cur = dummy;
            while(l1 && l2)
            {
                if(l1->val < l2->val)
                {
                    cur->next = l1;
                    l1 = l1->next;
                }else{
                    cur->next =l2;
                    l2 = l2->next;
                }
                cur = cur->next;
            }
            cur->next = (l1)?l1:l2;
            return dummy->next;
        }
    }
};

LeetCode 24. 两两交换链表中的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
/*
注意是交换节点,而不是值,关于链表题目,首先会发现链表头结点会改变,所以建一个虚拟头结点
*/
    ListNode* swapPairs(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy->next = head;//p指向要交换的两个节点的前面一个点
        for (auto p = dummy; p->next && p->next->next;) {
            auto a = p->next, b = a->next;
            p->next = b;
            a->next = b->next;
            b->next = a;
            p = a;
        }

        return dummy->next;
    }
};

LeetCode 25. K 个一组翻转链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
/*
上一道题是交换相邻两个元素,这道题是翻转相邻k个元素,头结点会改变,创建虚拟头结点,这道题和上一道题类似,弄一根指针,每次指向需要交换的k个元素的前一个元素
首先判断是否足够k
如果够k的话,翻转:
    其中先将k个节点内部的边反向
    然后是将前面的边搞定
    最后是后面的边
*/
    ListNode* reverseKGroup(ListNode* head, int k) {
        auto dummy = new ListNode(-1);//定义虚拟头结点
        dummy->next = head;
        for (auto p = dummy;;) {//从虚拟头结点开始遍历整个链表,p指向需要交换的k个元素的前一个元素
            auto q = p;//先遍历p后面够不够k个元素
            for (int i = 0; i < k && q; i ++ ) q = q->next;//往后走k步看q是否为空
            if (!q) break;//不足k个,这个for循环是常数次
            auto a = p->next, b = a->next;//指向每一组的前两个元素
            for (int i = 0; i < k - 1; i ++ ) {//要交换k-1个相邻的位置,要走k-1步
                auto c = b->next;//先存下来b的下一个元素
                b->next = a;
                a = b, b = c;
            }
            auto c = p->next;//先存下来p的下一个元素
            p->next = a, c->next = b;
            p = c;//结束之后p继续指向下一组k个元素的前一个位置
        }
        return dummy->next;
    }
};

LeetCode 26. 删除排序数组中的重复项

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        /*
        原地算法就是不能开数组,不能写递归
        双指针算法
        */
        int k = 0;
        for (int i = 0; i < nums.size(); i ++ )
            if (!i || nums[i] != nums[i - 1])
                nums[k ++ ] = nums[i];
        return k;
    }
};

LeetCode 27. 移除元素

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int k = 0;
        for (int i = 0; i < nums.size(); i ++ )
            if (nums[i] != val)
                nums[k ++ ] = nums[i];
        return k;
    }
};

LeetCode 28. 实现 strStr()

public:
/*
这道题是KMP的裸题,给定两个字符串s和p,输出p在s当中第一次出现的位置
KMP核心求next数组,最长前缀后缀,所有p[i-i]的相等的前缀和后缀中长度的最大值,不考虑平凡即只有自己的情况
*/
    int strStr(string s, string p) {
        if (p.empty()) return 0;
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;//字符串改成下标从1开始

        vector<int> next(m + 1);
        for (int i = 2, j = 0; i <= m; i ++ ) {
            while (j && p[i] != p[j + 1]) j = next[j];
            if (p[i] == p[j + 1]) j ++ ;
            next[i] = j;
        }

        for (int i &
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值