LeetCode刷题(七)——递归思维+滑动窗口

Day10 2020.07.31

1.reverse-string

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

  • 解法一:双指针
class Solution{
public:
    void reverseString(vector<char>& s) {
        if(s.size()<=1) return;
        int i=0,j=s.size()-1;
        while(j>i){
            swap(s[i],s[j]);
            i++;
            j--;
        }
    }
};
  • 解法二:reverse函数
class Solution {
public:
    void reverseString(vector<char>& s) {
        if(s.size()<=1) return;
        reverse(s.begin(),s.end());
    }
};
  • 解法三:递归
    需要用到一个递归栈,空间复杂度为O(n)
class Solution{
public:
    void reverseHelper(vector<char>& s,int pos,int len){
        if(pos>=len/2) return;
        swap(s[pos],s[len-pos-1]);
        reverseHelper(s,pos+1,len);
    }
    void reverseString(vector<char>& s) {
        if(s.size()<=1) return;
        reverseHelper(s,0,s.size());
    }
};
2.swap-nodes-in-pairs

将给定的链表中每两个相邻的节点交换一次,返回链表的头指针
例如:给出1->2->3->4,你应该返回链表2->1->4->3。
你给出的算法只能使用常量级的空间。你不能修改列表中的值,只能修改节点本身。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head||!head->next) return head;
        ListNode* p=head;
        head=p->next;
        p->next=head->next;
        head->next=p;
        //迭代之后要和之前的链表连接起来!!!
        p->next=swapPairs(p->next);
        return head;
    }
};
3.unique-binary-search-trees-ii

给定一个值n,请生成所有的存储值1…n.的二叉搜索树(BST)的结构
例如:给定n=3,你的程序应该给出下面五种不同的二叉搜索树
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]

class Solution {
public:
    vector<TreeNode*> generateTreesHelper(int start,int end){
        vector<TreeNode*> Trees;
        if(start>end){
            Trees.emplace_back(nullptr);
            return Trees;
        }
        for(int i=start;i<=end;i++){
            vector<TreeNode*> leftTrees=generateTreesHelper(start,i-1);
            vector<TreeNode*> rightTrees=generateTreesHelper(i+1,end);
            //从左子树集合中选出一颗左子树,右边相同操作,拼接到根上
            for(auto& left:leftTrees){
                for(auto& right:rightTrees){
                    TreeNode* cur=new TreeNode(i);
                    cur->left=left;
                    cur->right=right;
                    Trees.emplace_back(cur);
                }
            }
        }
        return Trees;
    }
    
    vector<TreeNode*> generateTrees(int n) {
        return generateTreesHelper(1,n);
    }
};
4.fibonacci-number
class Solution {
public:
    int fib(int N) {
        if(0==N) return 0;
        if(1==N) return 1;
        return fib(N-1)+fib(N-2);
    }
};
5.minimum-window-substring

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
滑动窗口算法的思路是这样:
1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。
2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。
需要思考的问题:
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
增加window计数器以及window里面的元素;
2、什么条件下,窗口应该暂停扩大,开始移动 left 缩小窗口?
当valid满足need的即所有元素都出现在窗口中;
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
减少window计数器以及window里面的元素;
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
应该在收缩窗口的时,且在收缩之前更新最终结果。
作者:labuladong 力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong-yong-si-xiang-by-/
窗口中一直只有T中包含的字符,是由right和left记录S中包含T中所有字符的子串的起始

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char,int> need,window;
        for(char c:t) need[c]++;

        int left=0,right=0;
        int valid=0;
        //记录最小覆盖子串的起始索引及长度
        int start=0,len=INT_MAX;
        while(right<s.size()){
            char c=s[right];
            //窗口右移
            right++;

            //更新窗口相关的数据
            if(need.count(c)){//need中存在c对应的字符
                window[c]++;
                if(window[c]==need[c]) valid++;
            }

            //判断左窗口是否要收缩
            while(valid==need.size()){
                //更新最小覆盖子串
                if(right-left<len){
                    start=left;
                    len=right-left;
                }
                //d记录将移出窗口的字符
                char d=s[left];
                left++;//窗口左移
                //进行窗口内数据的一系列更新
                if(need.count(d)){
                    if(window[d]==need[d]) valid--;
                    window[d]--;
                }
            }
        }
        return len==INT_MAX?"":s.substr(start,len);
    }
};
6.permutation-in-string

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False

  • 修改了minimum-window-substring问题中窗口收缩的时机,同时由于不需要返回子串,所以也不需要start和len记录匹配的子串起始点和长度。
class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map<char,int> need,window;
        for(char c:s1) need[c]++;

        int right=0,left=0;
        int valid=0;
        while(right<s2.size()){
            char c=s2[right];
            right++;
            if(need.count(c)){
                window[c]++;
                if(window[c]==need[c]) valid++;
            }
            //移动 left 缩小窗口的时机是窗口大小大于 t.size() 时,应为排列嘛,显然长度应该是一样的
            while(right-left>=s1.size()){
                if(valid==need.size()) return true;//因为map是hush表,所以若valid==need.size(),则里面的字符一定为need里面字符的排序
                char d=s2[left];
                left++;
                if(need.count(d)){
                    if(window[d]==need[d]) valid--;
                    window[d]--;
                }               
            }
        }
        return false;
    }
};
7.find-all-anagrams-in-a-string

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:字母异位词指字母相同,但排列不同的字符串。不考虑答案输出的顺序。
示例 1:
输入:s: “cbaebabacd” p: “abc”
输出:[0, 6]
解释:起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。
示例 2:
输入:s: “abab” p: “ab”
输出:[0, 1, 2]
解释:起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        unordered_map<char,int> need,window;
        for(char c:p) need[c]++;

        int left=0,right=0;
        int valid=0;
        vector<int> res;
        while(right<s.size()){
            char c=s[right];
            right++;
            if(need.count(c)){
                window[c]++;
                if(window[c]==need[c]) valid++;
            }
            while(right-left>=p.size()){
                if(valid==need.size()) res.emplace_back(left);
                char d=s[left];
                left++;
                if(need.count(d)){
                    if(window[d]==need[d]) valid--;
                    window[d]--;
                }
            }
        }
        return res;
    }
};
8.longest-substring-without-repeating-characters

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> window;
        int left=0,right=0;
        int Longest=0;
        while(right<s.size()){
            char c=s[right];
            right++;
            window[c]++;
            //c在窗口中出现不止一次,证明窗口要收缩了
            while(window[c]>1){
                char d=s[left];
                left++;
                window[d]--;
            }
            Longest=max(Longest,right-left);
        }
        return Longest;
    }
};

你可能感兴趣的:(刷题,算法)