【Leetcode热题】打卡 day1——10(完结)

目录

1、两数之和 - 哈希表

2、两数相加 - 链表

3、无重复字符的最长子串 - 滑动窗口

4、寻找两个正序数组的中位数 - 暴力合并两数组

5、最长回文子串 - 中心扩展法 + dp动态规划

(1)中心扩展法

(2)dp

6、三数之和 - 双指针 + 判重

7、盛水最多的容器 - 思维 + 双指针

8、电话号码的字母组合 - dfs + 组合

9、删除链表的倒数第N个节点 - 链表 + 快慢指针 / 栈

(1)暴力 计算链表长度

(2)快慢指针

(3)栈

10、有效的括号 - 暴力 + 栈 + 哈希表

(1)暴力+栈

(2)哈希表+栈


1、两数之和 - 哈希表

1. 两数之和

3fee9108bc5e423c8191a5c6c8ee1397.jpg

思路:

建立map,mp[nums[i]]=i 存储值所对应的下标

顺序遍历每一个元素,先查找mp中是否存在与nums[i]匹配的值(target-nums[i])

如果存在,则返回【i,匹配值下标i】

否则将该nums[i]存入map中

为什么先查找,再存哈希表?

因为题目要求,数组中同一元素不能重复出现,所以当遍历到自己时,先查找之前有没有存在的匹配值,再进行存储,这样避免了匹配到自己的情况

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> mp = new HashMap<>();
        for(int i=0; i < nums.length; i++ )
        {
            int t = target - nums[i];
            if(mp.containsKey(t)) return new int[] {mp.get(t),i};
            mp.put(nums[i],i);
        }
        return new int[] {};
    }
}
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n=nums.size();
        map<int,int> mp;
        for(int i=0;i<n;i++)
        {
            int t=target-nums[i];
            if(mp.find(t)!=mp.end())
            return {i,mp.find(t)->second};
            mp[nums[i]]=i;
        }
        return{};
    }
};

2、两数相加 - 链表

2. 两数相加

fd064709c3a045c9a924188aa0fed129.png

思路:

l1和l2指针顺序向后遍历两链表 

如果指到非空节点,存下各位之和sum和进位数t

注意:如果遍历到最后仍有进位,需要再在链表后新增节点


class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr,*tail = nullptr;
        int t = 0;
        while(l1||l2)
        {
            int x1 = l1? l1->val:0;
            int x2 = l2? l2->val:0;
            int sum = (x1+x2+t)%10;
            t = (x1+x2+t)/10;
            if(!head)
                head = tail = new ListNode(sum);
            else{
                tail->next = new ListNode(sum);
                tail = tail->next;
            }
            if(l1) l1 = l1->next;
            if(l2) l2 = l2->next;
        }
        if(t>0) tail->next = new ListNode(t);
        return head;
    }
};

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode dummy=new ListNode();
    ListNode cur=dummy;
    int t=0;
    while(l1!=null||l2!=null||t>0)
    {
        if(l1!=null) t+=l1.val;
        if(l2!=null) t+=l2.val;
        cur=cur.next=new ListNode(t%10);
        t=t/10;
        if(l1!=null) l1=l1.next;
        if(l2!=null) l2=l2.next;
    }
    return dummy.next;
    }
}

3、无重复字符的最长子串 - 滑动窗口

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

7e7e5f2865a24db0ac43432a366076fa.png

思路:

用set判重,双指针实现滑动窗口

用maxx更新最长长度

如果窗口内出现重复元素,缩左边界,并将不在窗口内的字符删去,直至窗口内无重复元素

即若窗口内无重复元素,向右增大窗口,并更新最大长度,直至出现重复元素,缩小窗口,最后滑窗遍历完整个字符串,取最大窗口长度

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    set<char> word;
    int st=0,maxx=0;
    for(int ed=0;ed<s.size();ed++)
    {
        while(word.count(s[ed]))
        {
            word.erase(s[st]);
            st++;
            
            if(!word.count(s[ed]))continue;
        }
        if(ed-st+1>maxx) maxx=ed-st+1;
        word.insert(s[ed]);
        cout<<ed<<' '<<st<<endl;
    }
    return maxx;
    }
};

4、寻找两个正序数组的中位数 - 暴力合并两数组

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

0620b1af82f444dc9933e7e159c7f700.png

思路:

暴力求解,没啥好说的,进阶的不想看 

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    int m=nums1.size(),n=nums2.size();
    cout<<m<<" "<<n;
    int t=m+n+10;
    int arr[t];
    
    int l1=0,l2=0,cnt=0;
    while(l1<m&&l2<n)
    {
        if(nums1[l1]>nums2[l2]) arr[cnt++]=nums2[l2],l2++;
        else arr[cnt++]=nums1[l1],l1++;
    }
    while(l1<m) arr[cnt++]=nums1[l1],l1++;
    while(l2<n) arr[cnt++]=nums2[l2],l2++;
    if((m+n)%2) return arr[(m+n-1)/2];
    else return (arr[(m+n)/2]+arr[(m+n)/2-1])*1.0/2;
    }
};

5、最长回文子串 - 中心扩展法 + dp动态规划

最长回文子串

759cf22fdadc4fdd9232525ca877968f.png

常规暴力O(n^3) 会tle 

(1)中心扩展法

思路:

枚举每一个字符s[i],同时设置左右指针l=i-1,r=i+1,最长回文子串长度len

如果该子串为回文串,会有如下三种情况:

  • 左指针和中心字符相等,len+1,例如:abaacb
  • 右指针和中心字符相等,len+1,例如:abcaab
  • 左指针和右指针字符相等,len+2,如:abcacb

如果左右指针不越界,循环拓展以上三种情况,并实时更新最长回文子串长度

因为遍历的是以每一个字符为中心扩展的情况,因此遍历完字符串,所有情况的子串情况也都考虑到了,时间复杂度O(n^2)

class Solution {
public:
    string longestPalindrome(string s) {
    
    string res="";
    int n=s.size();
    
    for(int i=0;i<n;i++)
    {
        int len=1;
        int l=i-1,r=i+1;
        while(l>=0 && s[l]==s[i])
            len++,l--;
        while(r<n && s[r]==s[i])
            len++,r++;
        while(l>=0 && r<n && s[l]==s[r]) 
            len+=2,l--,r++;
        if(len>res.size())
            res=s.substr(l+1,len);
        
    }
    return res;
    }

};

(2)dp

思路:

  • dp[i][j]:表示区间范围[i,j]的子串是否是回文子串,如果dp[i][j]为true,否则为false
  • 初始化dp[i][i] = true,因为单个字符自己就是回文串

只考虑s[i]和s[j]相等的情况,因为不相等根本就不可能为回文串(初始化dp全为false,所以只要把所有是回文串的情况标为true即可)

dp是以小范围的状态去更新大范围的状态,因此需要用短子串去更新长子串

dp[i][j]的状态取决于dp[i+1][j-1],如果i和j中间就1个字符,那必然是回文串,即就是j-1-(i+1)+1<2,化简即为j - i < 3,也就是说j - i < 3的情况dp[i][j]必为true

其他情况则用dp[i+1][j-1]来更新

最后遍历所有dp情况,如果dp=true,则更新最长回文子串长度

class Solution {
    public String longestPalindrome(String s) {
    int n=s.length();
    if(n<2) return s;
    
    boolean[][] dp=new boolean[n][n];
    int maxlen=1,st=0;
    
    for(int i=0;i<n;i++)
        dp[i][i]=true;
        
    for(int j=1;j<n;j++)
        for(int i=0;i<j;i++)
        {
            if(s.charAt(i)==s.charAt(j))
            {
                if(j-i<3) dp[i][j]=true;
                else dp[i][j]=dp[i+1][j-1];
            }
            
            if(dp[i][j] && j-i+1>maxlen)
            {  
                st=i;
                maxlen=j-i+1;
            }
        }
     
     return s.substring(st,st+maxlen);
    }
}

6、三数之和 - 双指针 + 判重

15. 三数之和

5afff048d5dd4a159c6dce3162e312e6.png

这题难点是:不能出现重复情况

思路:

首先对数组进行升序排序,因为三数之和=0,排序后能更好地去掉不可能为0的方案

遍历数组中每一个数作为第一个数x1

  • 如果x1>0,后面的数都比x1大,不可能有=0的方案存在
  • 如果遍历到的x1和前一个遍历过的元素值相同,则所生成的方案也是相同的,跳过
  • l 和 r 分别指向x1后的最前和最末尾元素,如果指向的三个之和=0则记录方案
  • 注意:如果s【l+1】和s【l】值一样,则需要跳过,因为s【l】作为x2的方案之前已经考虑过,而题目要求不能有重复情况,s【r】同理
  • (若两个数固定,剩下一个必固定,题目要求不能重复)
  • 如果三数之和sum>0,则r--
  • 如果sum<0,l++
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    int n=nums.size();
    sort(nums.begin(),nums.end());
    
    if(n<3) return res;
    
    for(int i=0;i<n;i++)
    {
        if(nums[i]>0) continue;
        if(i>0&&nums[i]==nums[i-1]) continue;
        int l=i+1,r=n-1;
        while(l<r){
        
        int sum=nums[i]+nums[l]+nums[r];
        
        if(sum==0)
        {
            res.push_back({nums[i],nums[l],nums[r]});
            while(l<r&&nums[l]==nums[l+1]) l++; //去重
            while(l<r&&nums[r]==nums[r-1]) r--;
            l++,r--; //若两个数固定,剩下一个必固定,题目要求不能重复,因此移动双指针
            
        }else if(sum>0) r--;
        else l++;
        }
    }
    return res;
    }
};

7、盛水最多的容器 - 思维 + 双指针

11. 盛最多水的容器

da8f94e83744437eac57683861a3d0f4.png

思路:

从两侧枚举板子
因为装水的高度取决于短板
若移动短板,则短板为min(h[i],h[j]),短板有可能变长,水体积可能增加
若移动长板,就算长板更长,也无法改变水的体积,因此水体积减小(间距变小),因此绝对不能移动长板 

class Solution {
public:
    int maxArea(vector<int>& h) {

    // 从两侧枚举板子
    // 因为装水的高度取决于短板
    // 若移动短板,则短板为min(h[i],h[j]),短板有可能变长,水体积可能增加
    // 若移动长板,就算长板更长,也无法改变水的体积,因此水体积减小(间距变小),因此绝对不能移动长板
    int i=0,j=h.size()-1;
    int res=0;
    
    while(i<j)
    {
        res= h[i]>h[j]?
        max(res,(j-i)*h[j--]):max(res,(j-i)*h[i++]);
    }
    return res;
    }
};

8、电话号码的字母组合 - dfs + 组合

17. 电话号码的字母组合

610598572e464b2e84869a1a416b46e8.png

思路:

【leetcode学习计划】算法入门(14 / 14)完结!_leetcode算法学习计划_Roye_ack的博客-CSDN博客

这题就是dfs组合问题的升级版

根据题意,digits长度就是每个组合的长度,我们可以枚举每一个位置的数字

用哈希表存每个数字对应的字母

如果每个位置都枚举完,则将该种情况存入答案

class Solution {
public:
    vector<string> tele={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    vector<string> res;
    string t;
    
    void dfs(int pos,string digits)
    {
        if(pos==digits.size())
        {
            res.push_back(t);
            return;
        }
        int num=digits[pos]-'0';
        for(int i=0;i<tele[num].size();i++)
        {
            t.push_back(tele[num][i]);
            dfs(pos+1,digits);
            t.pop_back();
        }
    }
    
    vector<string> letterCombinations(string digits) {
    if(digits.size()==0) return res;
    dfs(0,digits);
    return res;
    }
};

9、删除链表的倒数第N个节点 - 链表 + 快慢指针 / 栈

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

fe0547ea3f1c421f9d96e3c232775356.jpg

(1)暴力 计算链表长度

移动到要删除的节点的前一个,然后将该节点指向待删除节点的下一个,实现节点删除

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummy=new ListNode(0);
    dummy->next=head;
    ListNode* cur=head;
    int len=0;
    while(cur!=nullptr)
    {
        len++;
        cur=cur->next;
    }
    
    cur=dummy;
    for(int i=0;i<len-n;i++)
    {
        cur=cur->next;
    }
    cout<<cur->val<<endl;
    cur->next=cur->next->next;
    return dummy->next;
    }
};

(2)快慢指针

  • 定义快指针q和慢指针p,两者初始同时指向虚拟节点dummy
  • 先移动快指针q使pq间隔n个元素,随后同时移动pq指针,直至q指向链表末尾null,此时,p指针的下一个节点就是待删除节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode p=dummy,q=dummy;
        for(int i=0;i<n+1;i++)
            q=q.next;
        while(q!=null)
        {
            p=p.next;
            q=q.next;
        }
        p.next=p.next.next;
        return dummy.next;

    }
}

(3)栈

删除倒数第n个节点,可以利用栈的特性,让所有元素都入栈,然后扔出栈中n个节点,此时栈顶元素就是待删除的节点的前一个,然后再执行删除节点操作

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        stack<ListNode*> stk;
        ListNode* cur = dummy;
        while (cur) {
            stk.push(cur);
            cur = cur->next;
        }
        for (int i = 0; i < n; ++i) {
            stk.pop();
        }
        ListNode* prev = stk.top();
        prev->next = prev->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

 

10、有效的括号 - 暴力 + 栈 + 哈希表

20. 有效的括号

(1)暴力+栈

class Solution {
    public boolean isValid(String s) {
    if(s.length()%2==1) return false;
    Deque<Character> stk=new LinkedList<>();
    for(int i=0;i<s.length();i++)
    {
        char c=s.charAt(i);
        if(c=='('||c=='['||c=='{')
            stk.push(c);
         else if(c==')')
         {
             if(stk.isEmpty()||stk.peek()!='(') return false;
             stk.pop();
         }else if(c==']')
         {
             if(stk.isEmpty()||stk.peek()!='[') return false;
             stk.pop();
         }else if(c=='}')
         {
             if(stk.isEmpty()||stk.peek()!='{') return false;
             stk.pop();
         }
    }
    if(!stk.isEmpty()) return false;
    return true;
    }
}

(2)哈希表+栈

class Solution {
public:
    bool isValid(string s) {
    int n=s.size();
    if(n%2) return false;
    
    stack<char> stk;
    unordered_map<char,char> mp={
        {')','('},
        {']','['},
        {'}','{'}
    };
    
    for(int i=0;i<n;i++)
    {
        if(mp.count(s[i]))
            if(stk.empty()||mp[s[i]]!=stk.top())
            return false;
            else stk.pop();
        else stk.push(s[i]);
    }
    return stk.empty();
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值