LeetCode刷题

本文总结了在LeetCode上刷题的经验,涉及数组、链表、字符串、回文、动态规划等算法问题,如两数之和、两数相加、无重复字符的最长子串、寻找两个正序数组的中位数等,分析了各种问题的解决方案和时间复杂度,旨在提升算法能力和编程技巧。
摘要由CSDN通过智能技术生成

最近准备找实习,开始刷LeetCode~
注:不会写的题都参考了ACWing

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

你可以按任意顺序返回答案。

思路:

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

代码:

C++

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        unordered_map<int,int>hash;
        for(int i=0;i<nums.size();++i){
            int j=target-nums[i];
            if(hash.count(j)){
                res={i,hash[j]};
                break;
            }
            hash[nums[i]]=i;
        }
        return res;
    }
};

2. 两数相加

给你两个非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

思路:

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

代码:

C++

/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        auto dummy=new ListNode(-1), cur=dummy;
        int t=0;
        while(l1 || l2 || t){
            if(l1) t += l1->val, l1 = l1->next;
            if(l2) t += l2->val, l2 = l2->next ;
            
            /*
            auto tmp=new ListNode(t%10); //新建一个节点,表示下一位
            cur->next = tmp; //cur的next指针指向新节点
            cur = tmp; //cur移向下一位
            */
            
			cur = cur->next = new ListNode(t % 10);
			
            t=t/10; //进位
        }
        return dummy->next;
    }
};

注:

auto tmp=new ListNode(t%10);

tmp是ListNode指针类型,也就是auto是ListNode*而不是ListNode。指针类型访问成员变量要用"->",不能用"."。

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

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

思路:

双指针算法。[l,r]区间内字符最多出现一次,如果出现两次,就将l指针不停向后移,直到只出现一次。

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

代码:

C++

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int maxn=0;
        unordered_map<char,int>cnt;
        for(int l=0,r=0;r<s.size();++r){
            ++cnt[s[r]];
            while(cnt[s[r]]==2){
                --cnt[s[l++]];
            }
            maxn=max(maxn,r-l+1);
        }
        return maxn;
    }
};

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

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。

进阶: 你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

思路:
时间复杂度 O ( N ) O(N) O(N)

代码:


5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

思路:

  1. 马拉车算法(思想类似于KMP),时间复杂度 O ( N ) O(N) O(N),专用于解决最长回文子串问题,不常用。
  2. 字符串哈希+二分,时间复杂度 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N)),指路ACWing139. 回文子串的最大长度
  3. 枚举,枚举中点,再用两个指针同时向两边延展(分奇偶),直到两个字符不同或某个指针走出边界为止,就可以找到以该中点为中心的最长回文串,时间复杂度 O ( n 2 ) O(n^2) O(n2)此题我们选用这种做法
  4. DP

代码:

C++

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; //O(N)
            if(res.size()<r-l-1) res=s.substr(l+1,r-l-1); //O(N)

            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;
    }
};

6. Z 字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

思路:
找规律

代码:

C++



10. 正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。

思路:
会存在很多匹配方案,因为*可以匹配多个字符。
要在所有匹配方案里判断是否有可行解存在。
如果搜索全部这些方案,复杂度是指数级别的,会超时。
动态规划!
将一类方案看做一个集合,状态表示和状态计算如下图。
这题的优化和完全背包一样。

在这里插入图片描述
时间复杂度 O ( N 2 ) O(N^2) O(N2)

代码:

C++

class Solution {
public:
    bool isMatch(string s, string p) {
        int n=s.size(), m=p.size();
        s=' '+s,p=' '+p;
        vector<vector<bool>> f(n+1,vector<bool>(m+1));
        f[0][0]=true;
        for(int i=0;i<=n;++i){
            for(int j=1;j<=m;++j){ //
                if(j+1<=m && p[j+1]=='*') continue;
                if(i && p[j]!='*'){
                    f[i][j]=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];
    }
};

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

思路:
在这里插入图片描述

dfs的过程:逐位填字母,每一位填完遍历下一位,直到填满位数。

回溯:每一位的可选字母全部填完,回到上一位,改变字母,再往下填。

时间复杂度 O ( 4 n × n ) O(4^n \times n ) O(4n×n):每一位最多有4种选择(“wxyz”),递归的时间复杂度 O ( 4 n ) O(4^n) O(4n),再乘上push_back答案一次是 O ( n ) O(n) O(n)

dfs函数第一个参数是digits,因为digits不是全局变量,所以要带着它跑。

代码:

C++

class Solution {
public:
    string str[10]={
        "", "", "abc", "def", 
        "ghi", "jkl", "mno",
        "pqrs", "tuv", "wxyz"};
    
    vector<string> res;
    
    vector<string> letterCombinations(string digits) {
        if(digits.empty()) return res;
        dfs(digits, 0, "");
        return res;
    }

    void dfs(string& digits, int u, string tmp){ 
        if(u==digits.size()){
            res.push_back(tmp);
            //return; //可省
        }else{
            for(auto e: str[digits[u]-'0']){
                dfs(digits, u+1, tmp+e);
            }
        }
    }
};

32. 最长有效括号

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

思路:
合法括号序列两个性质:

  1. 左右括号数相等
  2. 任意前缀中,左括号数>=右括号数

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

代码:

C++



44. 通配符匹配

给定一个字符串 s 和一个字符模式 p ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

思路:
类似第10题。
具体分析见下图。
在这里插入图片描述
时间复杂度 O ( N 2 ) O(N^2) O(N2)

代码:

C++

class Solution {
public:
    bool isMatch(string s, string p) {
        int n=s.size(), m=p.size();
        s=' '+s, p=' '+p;
        vector<vector<bool>> f(n+1, vector<bool>(m+1));
        f[0][0]=true;
        for(int i=0;i<=n;++i){
            for(int j=1;j<=m;++j){
                if(p[j]!='*'){
                    f[i][j]= i && f[i-1][j-1] && (s[i]==p[j] || p[j]=='?');
                }else{
                    f[i][j]=f[i][j-1] || i && f[i-1][j];
                }
            }
        }
        return f[n][m];
    }
};

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

思路:
在线算法的核心思想基于下面的事实:

如果整数序列 { a 1 , a 2 , . . . , a n } \{a_1, a_2, ..., a_n\} {a1,a2,...,an}的最大和子列是 { a i , a i + 1 , . . . , a j } \{ a_{i}, a_{i+1}, ..., a_{j} \} {ai,ai+1,...,aj},那么必定有 ∑ k = i l a k ≥ 0 \sum_{k=i}^{l} a_k \geq 0 k=ilak0 对任意 i ≤ l ≤ j i\le l\le j ilj 成立。因此,一旦发现当前子列和为负,则可以重新开始考察一个新的子列。

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

代码:

C++

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        long long tmp=0, maxn=-3e9;
        for(int i=0;i<nums.size();++i){
            tmp+=nums[i];
            maxn=max(maxn,tmp); //注意 此句在前
            tmp=max(tmp,0ll); //注意 此句在后
        }// 否则全负数会出问题
        return maxn;
    }
};

72. 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

思路:
动态规划-编辑距离问题,详见下图。
在这里插入图片描述
代码:

C++

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n=word1.size(), m=word2.size();
        word1=' '+word1, word2=' '+word2;
        vector<vector<int>> f(n+1, vector<int>(m+1));
        for(int i=1;i<=n;++i) f[i][0]=i;
        for(int j=1;j<=m;++j) f[0][j]=j;
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                f[i][j]=min(min(f[i-1][j],f[i][j-1])+1, f[i-1][j-1]+(word1[i]!=word2[j]));
            }
        }
        return f[n][m];
    }
};

206. 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

思路:

迭代:
在这里插入图片描述
递归:
在这里插入图片描述
两种方法的时间复杂度都是 O ( N ) O(N) O(N)

代码:

C++

迭代

/**
 * 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* reverseList(ListNode* head) {
        if(!head || !head->next) return head; //如果是空链表或只剩一个结点 就返回
        auto a=head,b=head->next;
        while(b){
            auto c=b->next;
            b->next=a; //b指向a
            a=b,b=c; //a、b各向后移
        }
        head->next=NULL;
        return a;
    }
};

递归

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head->next || !head) return head;
        
        auto tail=reverseList(head->next); 
        
        head->next->next=head; //让head->next反过来指向head(本来是指向NULL的)
        head->next=NULL; 
        
        return tail; //新的头结点(原链表的最后一个结点)
    }
};

92. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

思路:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

代码:

C++

/**
 * 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* reverseBetween(ListNode* head, int left, int right) {
        //if(!head || !head->next) return head;
        auto dummy=new ListNode(-1);
        dummy->next=head;
        auto a=dummy;
        for(int i=0;i<left-1;++i){ //a从dummy移动到left前一个结点
            a=a->next; 
        }
        auto b=a->next,c=b->next; //b指向left
        for(int i=0;i<right-left;++i){ //此过程结束后 b指向right
            auto d=c->next;
            c->next=b;
            b=c,c=d;
        }
        a->next->next=c; //left的next指针指向right后面的一个结点
        a->next=b; //a的next指针指向right
        return dummy->next;
    }
};

96. 不同的二叉搜索树

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

思路:

  1. 动态规划 O ( n 2 ) O(n^2) O(n2)
    状态表示: f [ n ] f[n] f[n] 表示 n个节点的二叉搜索树共有多少种。
    状态转移:左子树可以有 0 , 1 , … n − 1 0,1,…n−1 0,1,n1 个节点,对应的右子树有 n − 1 , n − 2 , … , 0 n−1,n−2,…,0 n1,n2,,0 个节点, f [ n ] f[n] f[n] 是所有这些情况的加和,所以 f [ n ] = ∑ k = 0 n − 1 f [ k ] ∗ f [ n − 1 − k ] f[n]=∑^{n−1}_{k=0}f[k]∗f[n−1−k] f[n]=k=0n1f[k]f[n1k]
    时间复杂度分析:状态总共有 n n n 个,状态转移的复杂度是 O ( n ) O(n) O(n),所以总时间复杂度是 O ( n 2 ) O(n^2) O(n2)
  2. 卡特兰数
    结论:n个节点的二叉搜索树一共有 1 n + 1 C 2 n n \frac{1}{n+1}C^n_{2n} n+11C2nn种。
    O ( n 2 ) O(n^2) O(n2)杨辉三角递推求组合数。

代码:

1.动态规划

class Solution {
public:
    int numTrees(int n) {
        vector<int>f(n+1);
        f[0]=1;
        for(int i=1;i<=n;++i){
            for(int j=0;j<i;++j){
                f[i]+=f[j]*f[i-j-1];
            }
        }
        return f[n];
    }
};

2.卡特兰数(注意爆int)

class Solution {
public:
    long long c[2005][2005]={0};
    int numTrees(int n) {
        int a=n*2,b=n;
        for(int i=0;i<=a;++i){
            for(int j=0;j<=i;++j){
                if(!j) c[i][j]=1;
                else c[i][j]=c[i-1][j]+c[i-1][j-1];
            }
        }
        return c[a][b]/(n+1);
    }
};

97. 交错字符串

给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。

两个字符串 s 和 t 交错的定义与过程如下,其中每个字符串都会被分割成若干非空子字符串:

s = s 1 + s 2 + . . . + s n s = s_1 + s_2 + ... + s_n s=s1+s2+...+sn
t = t 1 + t 2 + . . . + t m t = t_1 + t_2 + ... + t_m t=t1+t2+...+tm
∣ n − m ∣ ≤ 1 |n - m| \le 1 nm1
交错 是 s 1 + t 1 + s 2 + t 2 + s 3 + t 3 + . . . s_1 + t_1 + s_2 + t_2 + s_3 + t_3 + ... s1+t1+s2+t2+s3+t3+... 或者 t 1 + s 1 + t 2 + s 2 + t 3 + s 3 + . . . t_1 + s_1 + t_2 + s_2 + t_3 + s_3 + ... t1+s1+t2+s2+t3+s3+...
提示: a + b a + b a+b 意味着字符串 a a a b b b 连接。

思路:
(动态规划) O ( n 2 ) O(n^2) O(n2)
状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示 s1 的前 i i i 个字符和 s2 的前 j j j 个字符是否可以交错组成 s3 的前 i + j i+j i+j 个字符。
状态转移:如果 s 3 [ i + j ] s_3[i+j] s3[i+j] 匹配 s 1 [ i ] s_1[i] s1[i],则问题就转化成了 f [ i − 1 ] [ j ] f[i−1][j] f[i1][j];如果 s 3 [ i + j ] s_3[i+j] s3[i+j] 匹配 s 2 [ j ] s_2[j] s2[j],则问题就转化成了 f [ i ] [ j − 1 ] f[i][j−1] f[i][j1]。两种情况只要有一种为真,则 f [ i ] [ j ] f[i][j] f[i][j] 就为真。

时间复杂度分析:状态总共有 n 2 n^2 n2个,状态转移复杂度是 O ( 1 ) O(1) O(1)。所以总时间复杂度是 O ( n 2 ) O(n^2) O(n2)

代码:

C++

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n=s1.size(), m=s2.size();
        if(n+m!=s3.size()) return 0;
        s1 = ' ' + s1, s2 = ' ' + s2, s3 = ' ' + s3;
        vector<vector<bool>> f(n+1,vector<bool>(m+1));
        for(int i=0;i<=n;++i){
            for(int j=0;j<=m;++j){
                if(!i&&!j) f[i][j]=1;
                if(i && s1[i]==s3[i+j]) f[i][j]=f[i-1][j];
                if(j && s2[j]==s3[i+j]) f[i][j]=f[i][j] || f[i][j-1];
            }
        }
        return f[n][m];
    }
};

115. 不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。

思路:
在这里插入图片描述

时间复杂度 O ( N 2 ) O(N^2) O(N2)

代码:

C++

class Solution {
public:
    int numDistinct(string s, string t) {
        int n=s.size(), m=t.size();
        s=' '+s, t=' '+t;
        vector<vector<unsigned long long>> f(n+1, vector<unsigned long long>(m+1));
        for(int i=0;i<=n;++i) f[i][0]=1;
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                f[i][j]=f[i-1][j];
                if(s[i]==t[j]){ //选s[i]匹配
                    f[i][j]+=f[i-1][j-1];
                }
            }
        }
        return f[n][m];
    }
};

516. 最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

思路:
待填

代码:

C++

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n=s.size();
        vector<vector<int>> f(n, vector<int>(n));
        for(int len=1;len<=n;++len){
            for(int i=0;i+len-1<n;++i){
                int j=i+len-1;
                if(i==j) f[i][j]=1;
                else{
                    if(s[i]==s[j]) f[i][j]=f[i+1][j-1]+2;
                    f[i][j]=max(f[i][j], max(f[i+1][j],f[i][j-1]));
                }
            }
        }
        return f[0][n-1];
    }
};

583. 两个字符串的删除操作

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

思路:
答案 = (n - 最长公共子序列) + (m - 最长公共子序列)

代码:

C++

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n=word1.size(), m=word2.size();
        word1=' '+word1, word2=' '+word2;
        vector<vector<int>> f(n+1, vector<int>(m+1));
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                f[i][j]=max(f[i-1][j],f[i][j-1]);
                if(word1[i]==word2[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
            }
        }
        return n+m-2*f[n][m];
    }
};

思路:
时间复杂度 O ( N ) O(N) O(N)

代码:

C++


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值