leetcode

165 阅读21分钟

位运算

#. 判断 是否为2的幂次方
- 方法1:
// 2的幂次方 二进制 只有一个1 
n & (n - 1)// 即去除掉末尾的1

- 方法2:
n & (-n) = n

# -6 的正值 6 的二进制:
00000000 00000000 00000000 00000110

# 取反得反码:
11111111 11111111 11111111 11111001

# +1 得补码,即 -6 在计算机中的二进制表示:
11111111 11111111 11111111 11111010

异或

  1. 任何数和 00 做异或运算,结果仍然是原来的数,即 a⊕0=a。

  2. 任何数和其自身做异或运算,结果是 00,即 a⊕a=0。

  3. 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。

136. 只出现一次的数字

输入: [2,2,1]

输出: 1

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto e: nums) ret ^= e;
        return ret;
    }
};

查找表

350. 两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
思路: 为两个数组分别建立 map,用来存储 num -> count 的键值对,统计每个数字出现的数量。
class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size()<nums2.size()){
            return intersect(nums2, nums1);
    	}
    	unordered_map <int,int> m;
        for(n : nums1){
            ++m[n];
        }
        vertor<int> intersection;
        for(n : nums2){
            if(m.count(n)){
              intersection.push_back(n);
              m[n]--;
              /*如果m[n]=0了,则直接清除m里面的n*/
              if(!m[n]){
                m.erase(n)
              }
            }
        }
        return intersection;
    }
};

389. 找不同

给定两个字符串 s 和 t,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。

示例 1:

输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。
示例 2:

输入:s = "", t = "y"
输出:"y"
示例 3:

输入:s = "a", t = "aa"
输出:"a"
思路:首先遍历字符串 s,对其中的每个字符都将计数值加 1;然后遍历字符串 t,对其中的每个字符都将计数值减 1。当发现某个字符计数值为负数时,说明该字符在字符串 t 中出现的次数大于在字符串 s 中出现的次数,因此该字符为被添加的字符。

class Solution{
  public:
      char findTheDifference(string s, string t){
      vector<int> cnt(26);
      for(ch : s){
        cnt[ch - 'a']++;
      }
      for(ch : t){
        cnt[ch - 'a']--;
        if(cnt[ch - 'a']<0){
          return ch;
        }
      }
      return '';
    }
}

双指针

16.最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

截屏2021-05-18 上午8.40.14.png

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        int ans = nums[0]+nums[1]+nums[2];
        for(i=0; i<nums.size();i++){
            int start=i+1,end=nums.size()-1;
            while(start < end){
                int sum=nums[i]+nums[start]+nums[end];
                if( abs(target-sum) < abs(target-ans) )
                    ans=sum;
                if (sum < target)
                    start++;
                else if (sum >target)
                    end--;
                else
                    return ans;
            }
        }
        return ans;
    }
};

167. 两数之和 II - 输入有序数组

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:27 之和等于目标数 9 。因此 index1 = 1, index2 = 2

哈希表,目的在于找到target

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        unordered_map<int,int> hashtable;
        for(int i=0;i<numbers.size();i++){
            auto it=hashtable.find(target - numbers[i]);
            if(it != hashtable.end())
                return {it->second+1,i+1}
            hashtable[numbers[i]]=i;
        }
        return {}; 
    }
};

88. 合并两个有序数组

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
思路:从后往前的双指针思路,先定义指针 i 和 j 分别指向数组中有值的位置的末尾,再定义指针 k 指向待填充的数组 1 的末尾。
然后不断的比较i和j指向的值的大小,迭代 i 和 j 指针。
如果 i 指针循环完了,j 指针的数组里还有值未处理的话,直接从 k 位置开始向前填充 j 指针数组即可。因为此时数组 1 原本的值一定全部被填充到了数组 1 的后面位置,且这些值一定全部大于此时 j 指针数组里的值。

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int i=m-1,j=n-1;
        int k=m+n-1;
        while(i>=0&&j>=0){
            if(nums1[i]>=nums2[j])
                nums1[k--]=nums1[i--];
            else nums1[k--]=nums2[j--];
        }
        while(j>=0){
            nums1[k--]=nums2[j--];
        }
    }       
}

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

截屏2021-05-19 下午4.47.40.png

思路:慢指针 j 从 0 开始,当快指针 i 遍历到非 0 元素的时候,i 和 j 位置的元素交换,然
后把 j + 1。

也就是说,快指针 i 遍历完毕后, [0, j) 区间就存放着所有非 0 元素,而剩余的[j,n]区间再遍历一次,用 0 填充满即可。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j=0;
        for(int i=0;i<nums.size();i++){
            if(nums[i]!=0)
                nums[j++]=nums[i];
        }
        while(j<nums.size()){
            nums[j++]=0;
        }
    }
}

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

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

/不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。/

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

截屏2021-05-20 上午9.24.05.png

思路:当快指针遇到的值域当前慢指针指向的值不一样时,慢指针前进一位,并且替换成快指针此时指向的值。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size()==0)
            return 0;
        int slow=0;
        for(int fast=0;fast<nums.size();fast++){
            if(nums[fast]!=nums[slow])
                nums[++slow]=nums[fast];
        }
        return slow++;
    }
};

524. 通过删除字母匹配到字典里最长单词

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。

输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]

输出: 
"apple"
思路:核心在于找 d 里面是否 存在的s的子序列(通过双指针)。
挨个遍历 d 里面的字符串 t ,先判断是否为 s 的子序列,如果是,再判断 t 的长度是否更长,或者长度一样但是字典序列更小,就进行替换


class Solution {
public:
    // 判断 t 是否为 s的子序列
    bool IsSubsequence(string s,string t){
        int j=0
        for(int i=0;i<s.size();i++){
            if(s[i]==t[j]) j++;
        }
        // 如果j==t.size(),则j指向了t的末尾,说明t为s的子序列
        return j==t.size();
    }
public:
    string findLongestWord(string s, vector<string>& dictionary) {
        string res="";
        // 遍历dictionary
        for(string t : dictionary){
            // 如果t为s的子序列
            if(IsSubsequence(s,t)){
                // t的长度更长 或者 长度一样长但是序列更小
                if(res.size()<t.size() || res.size()==t.size()&&res>t){
                    res=t;
                }
            }
        }
        return res;
    }
};

125. 验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。(说明:本题中,我们将空字符串定义为有效的回文串。)

输入: "A man, a plan, a canal: Panama"
输出: true

输入: "race a car"
输出: false
思路:先去掉非字母和数字的字符,再来进行判断,注意每次都要以start<end为前提

class Solution {
public:
    bool isPalindrome(string s) {
        int start=0;
        int end=s.size()-1;
        while( start<end ){
            while(start<end && !isalnum(s[start]))
                start++;
            while(start<end && !isalnum(s[end]))
                end--;
            if( start<end ){
                if( tolower(s[start]!=tolower(s[end]))
                    return false;
                start++;
                end--;
            }
        }
      return true;      
    }
}

392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

输入:s = "abc", t = "ahbgdc"
输出:true

输入:s = "axc", t = "ahbgdc"
输出:false
思路: 将i 指针指向s,j指针指向t,当在t中成功匹配到s[i]时,i指针往后移,一旦i === s.length ,就代表 s 中的字符串全部按顺序在 t 中找到了,返回 trueclass Solution {
public:
    bool isSubsequence(string s, string t) {
    int i=0;
    for(int j=0;j<t.size();j++){
        if(s[i]==t[j]){
            i++;
        }
    }
    if(i==s.size()){
        return true;
    }else
        return false;
    }
}

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

image.png

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

输入:height = [1,1]
输出:1

输入:height = [4,3,2,1,4]
输出:16

输入:height = [1,2,1]
输出:2
思路:容纳的水量 = 两个指针指向的数字中较小值 * 指针之间的距离。在初始时,左右指针分别指向数组的左右两端,不断移动对应数字较小的那个指针,同时不断更新最大值。
因为:如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动 数字较小的那个指针。

class Solution {
public:
    int maxArea(vector<int>& height) {
        // 容纳最大值
        int max=0;
        // 临时存储容纳值
        int h=0;
        int start=0,end=height.size()-1;
        while(start <end){
            if(height[start]>=height[end]){
                h = height[end]*(end-start);
                end--;
            }else{
                h = height[start]*(end-start);
                start++;
            }
            max= h>max ? h:max;
        }
        return max
    }
}

455. 分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
思路:先把把饼干和孩子的需求都排序好,然后从最小的饼干分配给需求最小的孩子开始,不断的尝试新的饼干和新的孩子,这样能保证每个分给孩子的饼干都恰到好处的不浪费,又满足需求。
利用双指针不断的更新 i 孩子的需求下标和 j饼干的值,直到两者有其一达到了终点位置:如果当前的饼干不满足孩子的胃口,那么把 j++ 寻找下一个饼干;如果满足,那么 i++; j++,继续寻找下一个孩子和下一个饼干。


class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int i=0,j=0;
        while(i<g.size() && j<s.size()){
            if(g[i]<=s[j])
                i++;
            j++;
        }
        return i;
    }
};

240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。

image.png

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
思路:左下角元素 为所在列最大元素,所在行最小元素.
1.如果 左下角元素 > 目标值,则目标值一定在该行的上方, 左下角元素 所在行可以消去。
2.如果 左下角元素 <目标值,则目标值一定在该列的右方, 左下角元素 所在列可以消去。

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        // matrix.size()是非负数unsigned,与它进行运算或者判断,都会将其转换为非负数
        int i=matrix.size()-1;
        int j=0;
        while(i>=0&&j<matrix[0].size()){
            if(matrix[i][j]==target){
                return true;
            }else if(matrix[i][j]>target){
                i--;
            }else{
                j++;
            }
        }
        return false;
    }
};

滑动窗口

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

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

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 4:
输入: s = ""
输出: 0

截屏2021-06-02 下午7.41.20.png

方法一:推荐
思路:定义一个 map 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(!s.size()) return 0;
        unordered_map<char,int> hash;
        int ans=0;
        int left=0;
        for(int right=0;right<s.size();right++){
           // 如果hash里面有s[right]
           if(hash.find(s[right])!=hash.end()) {
               // 更新left值,为hash找到的值 即为s值中的下标
               left=max(left,hash[s[right]]);
           }
           ans=max(ans,right-left+1);
           //  hash 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复
           hash[s[right]]=right+1;
        }
        return ans;
    }
};


方法二:
思路:当遇到不存在的元素,就将该值插入队列,当遇到了相同的元素,我们只要把队列的包含该元素的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(!s.size()) return 0;
        unordered_set<char> lookup;
        int ans=0;
        int left=0,right=0;
        for(;right<s.size();right++){
            // 当在lookup找到了当前元素
            while(lookup.find(s[right])!=lookup.end()){
                // 移除lookup中找到的元素已经其左边的元素
                lookup.erase(s[left]);
                left++;
            }
            lookup.insert(s[right]);
            ans=max(ans,right-left+1);
        }
        return ans;

    }
};

438. 找到字符串中所有字母异位词

给定一个字符串 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" 的字母异位词。
思路:
1. 因为字符串中的字符全是小写字母,可以用长度为26的数组记录字母出现的次数
2. 设sL = len(s), pL = len(p)。记录p字符串的字母频次tableP,和s字符串前m个字母频次tableS
3. 若tableP和tableS相等,则找到第一个异位词索引 0
4. 继续遍历s字符串索引为[pL, sL)的字母,在tableS中每次增加一个新字母,去除一个旧字母
5. 判断tableP和tableS是否相等,相等则在返回值ans中新增异位词索引 i - pL + 1
注意:要先判断 如果 sL<pL 直接返回[]

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ans;
        // 用长度为26的数组记录字母出现的次数
        vector<int> tableS(26,0);
        vector<int> tableP(26,0);
        int sL=s.size();
        int pL=p.size();
        // 如果p的长度>s的长度,则直接返回空数组
        if(sL<pL) return ans;
        
        // 先记录p字符串的字母频次tableP,和s字符串前m个字母频次tableS
        for(int i=0;i<pL;i++){
            tableS[s[i]-'a']++;
            tableP[p[i]-'a']++;
        }
        // 若tableS和tableC相等,则找到第一个异位词索引 0
        if(tableS==tableP) ans.push_back(0);
        // 继续遍历s字符串索引为[pL, sL)的字母,
        for(int i=pL;i<sL;i++){
            // 在tableS中每次增加一个新字母
            tableS[s[i]-'a']++;
            // 去除一个旧字母
            tableS[s[i-pL]-'a']--;
            // 判断tableS和tableP是否相等,相等则在返回值ans中新增异位词索引 i - pL + 1
            if(tableS==tableP) ans.push_back(i-pL+1);
        }
        return ans;
    }
};

567. 字符串的排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的 子串 。(输入的字符串只包含小写字母;两个字符串的长度都在 [1, 10,000] 之间)

示例 1:

输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
示例 2:

输入: s1= "ab" s2 = "eidboaoo"
输出: False
思路:与上一提思路基本一致
class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int l1=s1.size();
        int l2=s2.size();
        if(l1 > l2) return false;
        vector<int> table1(26,0);
        vector<int> table2(26,0);
        for(int i=0;i<l1;i++){
            table1[s1[i]-'a']++;
            table2[s2[i]-'a']++;
        }
        if(table1==table2) return true;
        for(int i=l1;i<l2;i++){
            table2[s2[i]-'a']++;
            table2[s2[i-l1]-'a']--;
            if(table1==table2) return true;
        }
        return false;
    }
};

30. 串联所有单词的子串

给定一个字符串 s 和一些 长度相同 的单词 words 。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 09 开始的子串分别是 "barfoo""foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
思路:思路总结 map + 滑动窗口
1. 用targetMap来记录words里单词和对应的数量 (一个单词可能出现多次)
2. 如何遍历呢? 起始地址是 0 到 wordSize,然后按照wordSize去移动窗口,这么做的原因是能保证考虑了所有的情况
3. 当前遍历次数也维持一个 currMap 来记录当前words到对应的数量,对于每次档次遍历,维持窗口left = right = 起始地址
- 单词在targetMap里不存在: 不满足条件,则重置窗口,清空currMap,
- 单词在targetMap里存在: 增加计数,这里需要考虑两种情况
  - 要考虑如果数量超过了targetMap里,需要去收缩窗口,即左移left
  - 如果已经每个单词都满足即等于 numWords, 则插入left作为当前的答案

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;
        int n = s.size();

        // 边缘情况
        if (n <= 0 || words.empty())
        {
            return res;
        }

        // 单词的大小
        int wordSize = words[0].size();
        // 单词数量
        int numWords = words.size();

        // words的单词到次数的映射
        unordered_map<string, int> targetMap;
        for (string& word : words)
        {
            ++targetMap[word];
        }

        
        // 起点以 one_word来偏移,这样子就可以覆盖所有的情况
        for (int i = 0; i < wordSize; ++i)
        {
            // 记录当前统计的窗口的left和 right, 以及已匹配的单词个数 cnt
            int left = i;
            int right = i;
            int cnt = 0;

            // 当前窗口的映射表
            unordered_map<string, int> currMap;

            // 窗口移动
            while (right + wordSize <= n)
            {
                // 提取以right为边界的 wordSize为大小的单词
                string currWord = s.substr(right, wordSize);
                right += wordSize;

                if (targetMap.find(currWord) != targetMap.end())
                {
                    // 存在该单词
                    ++currMap[currWord];
                    ++cnt;
                    // 需要检查数量是否超过,超过则要左移left来缩小窗口
                    while (currMap[currWord] > targetMap[currWord])
                    {
                        string ts = s.substr(left, wordSize);
                        left += wordSize;
                        --cnt;
                        --currMap[ts];
                    }
                    if (cnt == numWords)
                    {
                        res.push_back(left);
                    }
                }
                else
                {
                    // 不存在单词
                    left = right;
                    cnt = 0;
                    currMap.clear();
                }
            }
        }

        return res;
    }
};

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
思路:定义两个下标 i、j 为左右边界,中间的子数组为滑动窗口。在更新窗口的过程中不断的更新窗口之间的值的和 sum。

当 sum < 目标值,说明值不够大,j++,右边界右移。
当 sum >= 目标值,满足条件,把当前窗口的大小和记录的最小值进行对比,更新最小值。并且 i++ 左窗口右移,继续找最优解。
当 j 超出了数组的右边界,循环终止。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        if(!nums.size()) return 0;
        int res=INT_MAX;
        int sum=0;
        int i=0,j=0;
        while(j<nums.size()){   
            sum += nums[j];
            while (sum >= target) {
                res = min(res, j - i + 1);
                sum -= nums[i];
                i++;
            }
            j++;
        }
        return res==INT_MAX?0:res;
    }
};

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思路:
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {


    }
};

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:

输入:s = "a", t = "a"
输出:"a"

截屏2021-06-16 下午8.41.12.png

class Solution {
public:
    string minWindow(string s, string t) {
        vector<int> need(128,0);
        int count = 0;  
        for(char c : t)
        {
            need[c]++;
        }
        count = t.length();
        int l=0, r=0, start=0, size = INT_MAX;
        while(r<s.length())
        {
            char c = s[r];
            if(need[c]>0)
                count--;
            need[c]--;  //先把右边的字符加入窗口
            if(count==0)    //窗口中已经包含所需的全部字符
            {
                while(l<r && need[s[l]]<0) //缩减窗口
                {
                    need[s[l++]]++;
                }   //此时窗口符合要求
                if(r-l+1 < size)    //更新答案
                {
                    size = r-l+1;
                    start = l;
                }
                need[s[l]]++;   //左边界右移之前需要释放need[s[l]]
                l++;
                count++;
            }
            r++;
        }
        return size==INT_MAX ? "" : s.substr(start, size);
    }
};

BFS广度优先搜索

1306. 跳跃游戏 III

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

示例 1:

输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 5 -> 下标 4 -> 下标 1 -> 下标 3 
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3 

示例 2:

输入:arr = [3,0,2,1,2], start = 2
输出:false
解释:无法到达值为 0 的下标 1 处。
思路:
1)我们初始时将 start 加入队列。
2)在每一次的搜索过程中,我们取出队首的节点 u,它可以到达的位置为 u + arr[u] 和 u - arr[u]。
3)如果某个位置落在数组的下标范围 [0, len(arr)) 内,并且没有被搜索过,则将该位置加入队尾。4)只要我们搜索到一个对应元素值为 0 的位置,我们就返回 True。
5)在搜索结束后,如果仍然没有找到符合要求的位置,我们就返回 False。

class Solution {
public:
    bool canReach(vector<int>& arr, int start) {
        if(arr[start]==0) return true;
        int n=arr.size();

        vector<bool> used(n);
        queue<int> q;
        // 将start加入队列
        q.push(start);
        // 并标记为搜索过
        used[start]=true;

        while(!q.empty()){
            // 拿到队列中的第一个元素的引用
            int i=q.front();
            // 并将第一个元素移除
            q.pop();
            // 如果i+arr[i]在[0, n)里,并且没有被搜索过
            if( i+arr[i]<n && !used[i+arr[i]]){
                // 如果搜索到了0,直接返回 true
                if(arr[i+arr[i]]==0) return true;
                // 否则,将i+arr[i]加入队列
                q.push(i+arr[i]);
                // 并标记为搜索过
                used[i+arr[i]]=true;
            }
            // 如果i-arr[i]在[0, n)里,并且没有被搜索过
            if( i-arr[i]>=0 && !used[i-arr[i]]){
                // 如果搜索到了0,直接返回 true
                if(arr[i-arr[i]]==0) return true;
                // 否则,将i+arr[i]加入队列
                q.push(i-arr[i]);
                // 并标记为搜索过
                used[i-arr[i]]=true;
            }
        }
        return false;
    }
};

动态规划

122. 买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3
思路:
1.当天手里没有股票=前一天没有股票||前一天有股票+当天卖出
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
2.当天手里有股票=前一天没有股票+当天买入||前一天有股票
dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][1]);
注意:每一天的状态只与前一天的状态有关,而与更早的状态都无关,因此我们不必存储这些无关的状态。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        // int dp[n][2];

        int dp0=0,dp1=-prices[0];
        for(int i=1;i<n;i++){
            dp0=max(dp0,dp1+prices[i]);
            dp1=max(dp0-prices[i],dp1);
        }
        // return dp[n-1][0];
        return dp0;
    }
};

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

image.png

思路:使用最少的硬币数=f(27) = min{f(27-2)+1, f(27-5)+1, f(27-7)+1}

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int max=amount+1;
        vector<int> dp(amount+1,max);
        int n=coins.size();
        if(!n) return -1;
        dp[0]=0;
        for(int i=1;i<=amount;i++){
            for(int j=0;j<n;j++){
                if(coins[j]<=i){
                    dp[i]=min(dp[i],dp[i-coins[j]]+1);
                }
            }
        }
        return dp[amount]==amount+1? -1 : dp[amount];
    }
};

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/is… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。