LeetCode-子数组-子字符串(也就是连续的序列)

1 解题思路

        子数组以及子字符串(子串)就是连续的序列。既然是连续,常使用到的方法就是使用滑动窗口,滑动窗口的滑动条件就是题目的要求,滑动条件可以借助有序的set、multiset或者无序的unordered_set等来实现。

        对于子串的最值问题的求解常常会使用动态规划的思想,找出状态转移方程是关键。

        滑动窗口参考链接:CSDN

        对比子序列的题目:CSDN

2 子数组题目

2.1 最大连续1的个数

1004. 最大连续1的个数 III

class Solution {
public:
    int longestOnes(vector& nums, int k) {
        int ones = 0;
        vector counter(2,0);
        queue q;
        for(auto& i : nums)
        {
            q.push(i);
            counter[i]++;            
            if (counter[0] <= k)
                ones = std::max(ones, counter[0]+counter[1]);
            else
            {
                while (counter[0] > k)
                {
                    counter[q.front()]--;
                    q.pop();
                }
            }    
        }

        return ones;
    }
};

2.2 绝对差不超过限制的最长连续子数组

1438. 绝对差不超过限制的最长连续子数组

//链接:https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/solution/jue-dui-chai-bu-chao-guo-xian-zhi-de-zui-5bki/
class Solution {
public:
    int longestSubarray(vector& nums, int limit) {
        //数组中可能存在重复的数据,所以使用multiset
        multiset s;
        int n = nums.size();
        int left = 0, right = 0;
        int ret = 0;
        while (right < n) 
        {
            s.insert(nums[right]);
            while (*s.rbegin() - *s.begin() > limit) 
            {
                s.erase(s.find(nums[left++]));
            }
            ret = max(ret, right - left + 1);
            right++;
        }
        return ret;
    }
};

2.3 滑动窗口的中位数

480. 滑动窗口中位数

class Solution {
public:
    vector medianSlidingWindow(vector& nums, int k) {
        vector r;
        multiset s;//因为需要排序且可能存在重复的数据,所以选择multiset
        int left = 0;        

        for(auto& i : nums)
        {
            s.insert(i);
            if (s.size() < k)
            {
                continue;
            }
            else if (s.size() > k)
            {
                //这里是关键,题目https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/ 与此操作类似
                s.erase(s.find(nums[left++]));
            }

            auto it = s.begin();
            double mid = 0.0;
            if (k & 0x01 == 1)
            {
                std::advance(it, k/2);
                mid = *it;
            }
            else
            {
                std::advance(it, k/2-1);
                mid += *it;
                std::advance(it, 1);
                mid += *it;
                mid /= 2;
            }
            
            r.push_back(mid);            
        }

        return r;
    }
};

2.4 最长连续递增序列

674. 最长连续递增序列

//使用滑动窗口
class Solution {
public:
    int findLengthOfLCIS(vector& nums) {
        int maxLen = 1, left = 0, right = 0;
        for (;right < nums.size();++right)
        {
            if (right > 0)
            {
                if (nums[right] > nums[right-1])
                    maxLen = std::max(maxLen, right-left+1);
                else
                    left = right;
            }
        }

        return maxLen;
    }
};

2.5 最长重复子数组

718. 最长重复子数组

class Solution {
public:
    int findLength(vector& nums1, vector& nums2) {
        int n = nums1.size(), m = nums2.size();
        if (n*m == 0)
            return 0;
        
        //dp[i][j]:nums1的前i个元素和nums2的前j个元素的最长公共子数组的长度
        vector> dp(n+1, vector(m+1,0));
        //base case 上面的默认值已经进行了初始化,这里不用再进行单独初始化了
        // for (int i=0;i<=n;++i)
        //     dp[i][0] = 0;
        // for (int i=0;i<=m;++i)
        //     dp[0][i] = 0;

        int maxLen = 0;
        for (int i=1;i<=n;++i) 
        {
            for (int j=1;j<=m;++j)
            {
                if (nums1[i-1] == nums2[j-1])
                {
                    dp[i][j] = 1 + dp[i-1][j-1];
                    maxLen = std::max(maxLen, dp[i][j]);
                }
            }
        }

        return maxLen;
    }
};

2.6 最大子数组和

53. 最大子数组和

剑指 Offer 42. 连续子数组的最大和

class Solution {
public:
    int maxSubArray(vector& nums) {
        int pre = nums[0], max = pre;
        for(int i=1;i < nums.size();i++){
            pre = std::max(pre + nums[i],nums[i]);
            max = std::max(max,pre);
        }
        return max;      
    }
};

2.7

2.8

2.9

2.10

2.11

3 子字符串(也就是子串)

3.1 最长不含重复字符的子字符串

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

剑指 Offer 48. 最长不含重复字符的子字符串

剑指 Offer II 016. 不含重复字符的最长子字符串

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set w;
        int left = 0, right = 0, len = 0;
        while(right < s.length())
        {
            char c = s[right];
            if (w.count(c))
            {
                len = std::max(len, right - left);                
                w.erase(s[left++]);
                continue;
            }
            else
            {
                w.insert(c);
            }
            ++right;
        }

        len = std::max(len, right - left);
        return len;
    }
};

动态规划的解答:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector dp(128,-1);//存储每个字符最后出现的位置
        int i=0,j=0,res=0;
        for(;j

3.2 最小覆盖子串

76. 最小覆盖子串

//从leetcode 567 https://leetcode-cn.com/problems/permutation-in-string/ 中的方法二修改而来
//因为方法二不要求字符串中全部是小写字母
class Solution {
public:
    string minWindow(string s, string t) {
        //方法一
        string result;
		unordered_map need, window;
		for (int i = 0;i

3.3 字符串的排列

567. 字符串的排列

剑指 Offer II 014. 字符串中的变位词

/*
滑动窗口
由于排列不会改变字符串中每个字符的个数,所以只有当两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列。

根据这一性质,记 s1 的长度为 n,我们可以遍历 s2 中的每个长度为 n 的子串,判断子串和 s1 
中每个字符的个数是否相等,若相等则说明该子串是 s1 的一个排列。

使用两个数组 cnt1 和 cnt2, cnt1 统计 s1 中各个字符的个数,cnt2统计当前遍历的子串中各个字符的个数。

由于需要遍历的子串长度均为 n,我们可以使用一个固定长度为n 的滑动窗口来维护 cnt2 :
滑动窗口每向右滑动一次,就多统计一次进入窗口的字符,少统计一次离开窗口的字符。
然后,判断 cnt1 是否与 cnt2相等,若相等则意味着 s1 的排列之一是 s2 的子串。

https://leetcode-cn.com/problems/permutation-in-string/solution/zi-fu-chuan-de-pai-lie-by-leetcode-solut-7k7u/
*/

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        //方法一
        // int n = s1.length(), m = s2.length();
        // if (n > m) {
        //     return false;
        // }
        // vector cnt1(26), cnt2(26);
        // for (int i = 0; i < n; ++i) {
        //     ++cnt1[s1[i] - 'a'];
        //     ++cnt2[s2[i] - 'a'];
        // }
        // if (cnt1 == cnt2) {
        //     return true;
        // }
        // for (int i = n; i < m; ++i) {
        //     ++cnt2[s2[i] - 'a'];
        //     --cnt2[s2[i - n] - 'a'];
        //     if (cnt1 == cnt2) {
        //         return true;
        //     }
        // }
        // return false;

        //方法二,速度稍低于方法一
		unordered_map need, window;
		for (int i = 0;i

3.4 字符串中的所有变位词

剑指 Offer II 015. 字符串中的所有变位词

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

class Solution {
public:
    vector findAnagrams(string s, string p) {
        vector r;
        do 
        {
            int n = p.length(), m = s.length();
            if (n > m) 
            {
                break;
            }

            vector cnt1(26), cnt2(26);
            for (int i = 0; i < n; ++i) 
            {
                ++cnt1[p[i] - 'a'];
                ++cnt2[s[i] - 'a'];
            }

            if (cnt1 == cnt2) 
            {
                r.push_back(0);
            }
            
            for (int i = n; i < m; ++i) 
            {
                ++cnt2[s[i] - 'a'];
                --cnt2[s[i - n] - 'a'];
                if (cnt1 == cnt2) 
                {
                    r.push_back(i-n+1);
                }
            }
        }while(0);

        return r;        
    }
};

3.5 重复的DNA序列

187. 重复的DNA序列

class Solution {
public:
    vector findRepeatedDnaSequences(string s) {
        int n = s.length();
        unordered_map m;
        unordered_set st;
        for (int i=0;i<=n-10;++i)
        {
            string str = s.substr(i,10);
            if (m.count(str))
                st.insert(str);
            m[str]++;
        }
        return vector(st.begin(), st.end());
    }
};

3.6 最长重复子串

1044. 最长重复子串

Rabin-Karp算法《算法导论3rd-p580》

参考链接:

https://leetcode-cn.com/problems/longest-duplicate-substring/solution/wei-rao-li-lun-rabin-karp-er-fen-sou-suo-3c22/

【字符串哈希】字符串哈希入门

//参考链接:https://leetcode-cn.com/problems/longest-duplicate-substring/solution/wei-rao-li-lun-rabin-karp-er-fen-sou-suo-3c22/
class Solution {
public:
	int n;
    //选择的是一个素数,相当于Rabin-Karp算法中的31进制,《算法导论3rd-p580》介绍的是十进制
	unsigned long long prime = 31;
	string longestDupSubstring(string s) {
		n = s.size();
		int l = 1;
		int r = n - 1;
		int pos = -1;
		int len = 0;

		auto find = [&](int len) 
        {
			unsigned long long hash = 0;
			unsigned long long power = 1;
			//将[0,len)这个len长度的字符串作为模式串,也就是RK算法中所说的P[1...m]《算法导论3rd-p580》
			for (int i = 0; i < len; i++) 
            {
				hash = hash * prime + (s[i] - 'a');
				power *= prime;
			}
			unordered_set exist{ hash };
			for (int i = len; i < n; i++) 
            {
				hash = hash * prime - power * (s[i - len] - 'a') + (s[i] - 'a');
				//如果已经存在该hash,则说明存在一个长度为 len 的字符串s.substr(i-len+1, len)等于 s.substr(0, len)
				if (exist.count(hash)) 
					return (i - len + 1);
				exist.insert(hash);
			}
			return -1;
		};

        //二分查找
		while (l <= r) 
        {
            //将索引[0,mid)范围内长度为mid字符串作为模式串,然后在[1, n-1](l的初始值为1;r的初始值为n-1)查找是否存在与模式串相同的字符串
			int mid = (l + r) / 2;
			int start = find(mid);
			if (start != -1) 
            {
                //在[1, n-1]内找到与模式串相同的子串,也就是存在重复子串,需要增加模式串的长度(也就是扩展上面的mid)来看是否存在更长的重复子串,要扩展模式串的长度就需要递增左边界
				len = mid;
				pos = start;
				l = mid + 1;
			}
			else 
            {
                //在[1, n-1]内没有找到与模式串相同的子串,此时需要缩短模式串的长度(也就是缩短上面的mid),要缩短模式串的长度就需要递减右边界
				r = mid - 1;
			}
		}

		if (pos == -1) 
            return "";
		else 
            return s.substr(pos, len);
	}
};

3.7 最长回文子串

5. 最长回文子串

class Solution {
public:
    string longestPalindrome(string s) {
        //方法一
        // string res;
        // for (int i = 0; i < s.size(); i++) 
        // {
        //     // 以 s[i] 为中心的最长回文子串
        //     string s1 = palindrome(s, i, i);
        //     // 以 s[i] 和 s[i+1] 为中心的最长回文子串
        //     string s2 = palindrome(s, i, i + 1);
        //     res = res.size() > s1.size() ? res : s1;
        //     res = res.size() > s2.size() ? res : s2;
        // } 
        // return res;

        //方法二,马拉车
		string T = preProcess(s);
		int n = T.length();
		int *P = new int[n];

		int C = 0, R = 0;
		int maxLen = 0, maxC = 0;
		for (int i = 1; i < n - 1; i++) 
		{
			int i_mirror = 2 * C - i;
			if (R > i) 
			{
				P[i] = std::min(R - i, P[i_mirror]);// 防止超出 R
			} 
			else 
			{
				P[i] = 0;// 等于 R 的情况
			}

			// 碰到之前讲的三种情况时候,需要利用中心扩展法
			while (T[i + 1 + P[i]] == T[i - 1 - P[i]]) 
			{
				P[i]++;
			}

			// 判断是否需要更新 R
			if (i + P[i] > R) 
			{
				C = i;
				R = i + P[i];
			}

			if (P[i] > maxLen)
			{
				maxLen = P[i];
				maxC = i;
			}
		}

		int start = (maxC - maxLen) / 2; //最开始讲的求原字符串下标
		return s.substr(start, maxLen);
    }

    string palindrome(string& s, int l, int r) {
        // 防止索引越界
        while (l >= 0 && r < s.size() && s[l] == s[r]) 
        {
            // 向两边展开
            l--; r++;
        } 
        // 返回以 s[l] 和 s[r] 为中心的回文子串
        return s.substr(l + 1, r - l - 1);
    }  

    //原字符串:abcba ===> ^#a#b#c#b#a#$
	string preProcess(string s) {
		int n = s.length();
		if (n == 0) 
		{
			return "^$";
		}

		string ret = "^";
		for (int i = 0; i < n; i++)
		{
			ret.push_back('#');
			ret.push_back(s[i]);
		}

		ret.append("#$");
		return ret;
	}     
};

3.8 回文子串的数目

647. 回文子串

class Solution {
public:
    int countSubstrings(string s) {
        int count = 0;
        for (int i = 0; i < s.size(); i++) 
        {
            count += palindrome(s, i, i) + palindrome(s, i, i + 1);
        } 

        return count;
    }

    int palindrome(string& s, int l, int r) {
        int count = 0;
        // 防止索引越界
        while (l >= 0 && r < s.size() && s[l] == s[r]) 
        {
            ++count;
            // 向两边展开
            l--; r++;
        } 
        return count;
    } 

    string palindromeString(string& s, int l, int r) {
        // 防止索引越界
        while (l >= 0 && r < s.size() && s[l] == s[r]) 
        {
            // 向两边展开
            l--; r++;
        } 
        return s.substr(l+1,r-l-1);
    }           
};

3.9 分割回文子串

131. 分割回文串

class Solution {
public:
    vector> partition(string s) {
        vector path;
        vector> result;
        helper(s,0,path,result);
        return result;
    }

    // [begin, end]
    void helper(const string& str, int begin, vector& path, vector>& result)
    {
        if (begin == str.size())
        {
            result.emplace_back(path);
            return;
        }

        for (int end=begin;end end)
        {
            return false;
        }

        while (begin < end)
        {
            if (str[begin++] != str[end--])
            {
                return false;
            }
        }

        return true;
    }    
};

3.10 最少回文分割

剑指 Offer II 094. 最少回文分割

132. 分割回文串 II

//参考题目:https://leetcode-cn.com/problems/M99OJA/
class Solution {
public:
    int minCut(string s) {
        //方法一:借鉴 剑指 Offer II 086. 分割回文子字符串,https://leetcode-cn.com/problems/M99OJA/ 超时
        // vector path;
        // int min = INT_MAX;
        // dfs(s,0,path,min);
        // return min-1;

        //方法二,https://leetcode-cn.com/problems/palindrome-partitioning-ii/solution/fen-ge-hui-wen-chuan-ii-by-leetcode-solu-norx/
        // https://leetcode-cn.com/problems/palindrome-partitioning-ii/solution/wei-rao-li-lun-yu-chu-li-dong-tai-gui-hu-akpu/
        /********************************************************************************************
        PalindromeTable[i][j] 表示 s[i..j] 是否为回文串
        dp[i] 表示 s[0..i] 的最小分割
        状态转移方程:dp[i] 遍历每一个j(0<=j> PalindromeTable(n, vector(n, true));
        
        //PalindromeTable[i][j]表示 s[i..j] 是否为回文串
        for (int i = n - 1; i >= 0; --i) 
        {
            for (int j = i + 1; j < n; ++j) 
            {   //从两边到中间来判断是否是回文
                PalindromeTable[i][j] = (s[i] == s[j]) && PalindromeTable[i+1][j-1];
            }
        }

        vector dp(n, INT_MAX);
        for (int i = 0; i < n; ++i) 
        {
            //字符串s[0][i]本来就是回文字符串,不需要分割,故 dp[i] = 0
            if (PalindromeTable[0][i]) 
            {
                dp[i] = 0;
            }
            else 
            {   // 0<=j& path, int& min)
    {
        if (begin == str.size())
        {
            min = min > path.size() ? path.size() : min;
            return;
        }

        for (int end=begin;end end)
        {
            return false;
        }

        while (begin < end)
        {
            if (str[begin++] != str[end--])
            {
                return false;
            }
        }

        return true;
    }    
};

3.11 最多删除一个字符得到回文

剑指 Offer II 019. 最多删除一个字符得到回文

680. 验证回文字符串 Ⅱ

class Solution {
public:
    bool validPalindrome(string s) {
        bool isValidPalindrome = false;
        int n = s.length(), outBegin = 0, outEnd = 0;
        isValidPalindrome = isPalindrome(s,0,n-1,outBegin,outEnd);
        if (isValidPalindrome)
        {
            return true;
        }
        else
        {
            int tmp1,tmp2;
            isValidPalindrome = (isPalindrome(s,outBegin,outEnd-1,tmp1,tmp2) || isPalindrome(s,outBegin+1,outEnd,tmp1,tmp2));
        }

        return isValidPalindrome;
    }

    // [begin, end]
    bool isPalindrome(const string& str, int begin, int end, int& outBegin, int& outEnd)
    {
        if (begin > end)
        {
            outBegin = begin;
            outEnd = end;            
            return false;
        }

        while (begin < end)
        {
            if (str[begin] != str[end])
            {
                outBegin = begin;
                outEnd = end;
                return false;
            }
            ++begin;
            --end;
        }

        return true;
    }    
};

3.12 将字符串反转到单调递增

926. 将字符串翻转到单调递增

剑指 Offer II 092. 翻转字符

class Solution {
public:
    int minFlipsMonoIncr(string s) {
        // 方法一
        // int n = s.length();
        // //dp[i][0]表示第i个元素被设置为0,使得s为单调递增的最小翻转次数
        // //dp[i][1]表示第i个元素被设置为1,使得s为单调递增的最小翻转次数
        // vector> dp(n, vector(2,0));
        // //base case
        // if (s[0]=='0')
        //     dp[0][0] = 0, dp[0][1] = 1;
        // else
        //     dp[0][0] = 1, dp[0][1] = 0;
        
        // for (int i=1;i dp[n-1][1] ? dp[n-1][1] : dp[n-1][0];

        // 方法二,不用dp数组,而用两个变量进行优化
        int n = s.length(), pre0, pre1, cur0, cur1;
        //base case
        if (s[0]=='0')
            pre0 = 0, pre1 = 1;
        else
            pre0 = 1, pre1 = 0;
        
        for (int i=1;i pre1 ? pre1 : pre0;
    }
};

3.13 

3.14

3.15

3.16

3.17

3.18

3.19

3.20

3.21

3.22

3.23

3.24

3.25

3.26

3.27

3.28

3.29

3.30

3.31

你可能感兴趣的:(#,leetcode)