双指针
滑动窗口
队列
时间复杂度:\(O(n)\)
空间复杂度:$ O(n) $
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int minSubArrayLen_que (int target, vector<int >& nums) { queue<int > que; int sum = 0 ; int minLen = INT32_MAX; for (int & num: nums) { sum += num; que.push (num); while (sum >= target) { if (minLen > que.size ()) minLen = (int ) que.size (); sum -= que.front (); que.pop (); } } return minLen == INT32_MAX ? 0 : minLen; }
滑动窗口
时间复杂度:\(O(n)\)
空间复杂度:$ O(1) $
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int minSubArrayLen (int target, vector<int >& nums) { int sum = 0 ; int minLen = INT32_MAX; for (int l = 0 , r = 0 ; r < nums.size (); r++) { sum += nums[r]; while (sum >= target) { if (minLen > r - l + 1 ) minLen = r - l + 1 ; sum -= nums[l++]; } } return minLen == INT32_MAX ? 0 : minLen; }
题目实际是求一个数组中,有最多两种不同值的最大连续子数组。所以可以使用滑动窗口来解决。
需要注意的是滑动窗口左边界的更新方式,比如:
1 2 1 2 1 2 2 2 3 3 其中 2 3 交界,左边界应该从 0 更新到 3,而不是 1 或者 5
滑动窗口 + 向左更新左边界
时间复杂度:\(O(m+n)\approx O(n),\; m<n\)
空间复杂度:\(O(1)\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int totalFruit (vector<int >& fruits) { int maxTotal = 0 ; int fruitTypes[] = {-1 , -1 }; if (fruits.size () <= 2 ) return (int ) fruits.size (); for (int l = 0 , r = 0 ; r < fruits.size (); r++) { if (fruitTypes[0 ] == -1 ) fruitTypes[0 ] = fruits[r]; else if (fruitTypes[1 ] == -1 && fruitTypes[0 ] != fruits[r]) fruitTypes[1 ] = fruits[r]; else if (fruitTypes[0 ] != fruits[r] && fruitTypes[1 ] != fruits[r]) { fruitTypes[0 ] = fruits[r - 1 ]; fruitTypes[1 ] = fruits[r]; l = r; while (fruits[l - 1 ] == fruitTypes[0 ]) l--; } if (maxTotal < r - l + 1 ) maxTotal = r - l + 1 ; } return maxTotal; }
滑动窗口+哈希表(unordered_map)/字典(map)
时间复杂度:\(O(m+n)\approx O(n),\; m < n\)
空间复杂度:$ O(n) $
使用一个哈希表或者字典,记录子数组水果种类和其数量。
当水果种类超过两种,则开始更新左边界:
不断减去左边界对应水果的数量,并让左边界向右移动。
当左边界对应的水果数量减少为0,则表示已经找到最新的左边界了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int totalFruit_Hashmap (vector<int >& fruits) { int maxTotal = 0 ; unordered_map<int , int > fruitTypeNum; for (int l = 0 , r = 0 ; r < fruits.size (); r++) { fruitTypeNum[fruits[r]]++; while (fruitTypeNum.size () > 2 ) { fruitTypeNum[fruits[l]]--; if (fruitTypeNum[fruits[l]] == 0 ) fruitTypeNum.erase (fruits[l]); l++; } maxTotal = max (maxTotal, r - l + 1 ); } return maxTotal; }
滑动窗口+哈希表
时间复杂度:\(O(m+n)\approx O(n),\; m<n\)
空间复杂度:\(O(n)\)
方法一
首先,因为要对字符串中的各个字符的数量进行数量的比较,所以使用两个哈希表进行记录。
右指针遍历字符串的同时,更新哈希表中字符数量。
使用变量typeNum
对满足条件的字符种类进行统计。
当typeNum
大小为字符串t
哈希表的大小时,说明找到一个覆盖子串。
比较长度,如果是最小子串则更新结果。
开始对左边界更新,直到不满足覆盖条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 string minSubString (string s, string t) { unordered_map<char , int > sCharCnt, tCharCnt; int minLen = INT32_MAX; string res; for (const char c: t) tCharCnt[c]++; int typeNum = 0 ; for (int l = 0 , r = 0 ; r < s.size (); r++) { sCharCnt[s[r]]++; if (tCharCnt.find (s[r]) != tCharCnt.end () && sCharCnt[s[r]] == tCharCnt[s[r]]) typeNum++; while (typeNum == tCharCnt.size ()) { if (minLen > r - l + 1 ) { minLen = r - l + 1 ; res = s.substr (l, r - l + 1 ); } if (tCharCnt.find (s[l]) != tCharCnt.end () && --sCharCnt[s[l]] < tCharCnt[s[l]] ) typeNum--; l++; } } return res; }
更为简洁的一种方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 string minSubString_abbr (string s, string t) { unordered_map<char , int > sCharCnt, tCharCnt; string res; for (char c: t) tCharCnt[c]++; for (int l = 0 , r = 0 , cnt = 0 ; r < s.size (); r++) { sCharCnt[s[r]]++; if (sCharCnt[s[r]] <= tCharCnt[s[r]]) cnt++; while (sCharCnt[s[l]] > tCharCnt[s[l]]) sCharCnt[s[l++]]--; if (cnt == t.length () && (res.empty () || res.length () > r - l + 1 )) res = s.substr (l, r - l + 1 ); } return res; }
这个方法的妙处就是,不去考虑种类的数量。而是统计s
子串中,属于t
的数量。
由于stl哈希表的特点:tCharCnt[s[r]]
如果tCharCnt
中没有键值为s[r]
的变量,
会创建一个{s[r], 0}
。
因此sCharCnt[s[r]] <= tCharCnt[s[r]]
仍然可以筛选掉不属于tCharCnt的键。
sCharCnt[s[l]] > tCharCnt[s[l]]
同理。
使用哈希表存储出现过的字符,利用滑动窗口的左右指针来得到无重复字符串的长度,然后记录最小的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int lengthOfLongestSubstring (const string& s) { int left = 0 , right = 0 ; int res = 0 ; unordered_map<char , int > hash; while (right < s.length ()) { hash[s[right++]]++; while (hash[s[right - 1 ]] > 1 ) hash[s[left++]]--; if (right - left > res) res = right - left; } return res; }
快慢指针
暴力法
时间复杂度:$ O(n^2) $
空间复杂度:$ O(1) $
每删除一个目标值,就将其之后的全部值向前覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int removeElement_brute (vector<int >& nums, int val) { int length = static_cast <int >(nums.size ()); for (int i = 0 ; i < length; i++) { if (nums[i] == val) { for (int j = i; j < length - 1 ; j++) nums[j] = nums[j+1 ]; length--; i--; } } return length; }
快慢指针法
时间复杂度:$ O(n) $
空间复杂度:\(O(1)\)
S,F分别代表慢指针和快指针。
例1:删除一个元素2
例2:删除两个元素2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int removeElement (vector<int >& nums, int val) { int slowIndex{0 }; for (int fastIndex = 0 ; fastIndex < nums.size (); fastIndex++) { if (nums[fastIndex] != val) nums[slowIndex++] = nums[fastIndex]; } return slowIndex; }
使用快慢指针的方法,从序号为 1 的数开始:
如果 num[fast]
≠ num[fast-1]
,说明没有重复。
否则说明num[fast]
与上一个值重复了,需要删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int removeDuplicates (vector<int >& nums) { int length = (int ) nums.size (); if (length == 0 ) return 0 ; int slow = 1 ; for (int fast = 1 ; fast < length; fast++) { if (nums[fast] != nums[fast - 1 ]) { nums[slow++] = nums[fast]; } } return slow; }
1 2 3 4 5 6 7 8 9 void moveZeroes (vector<int >& nums) { int slow = 0 ; for (int fast = 0 ; fast < nums.size (); fast++) { if (nums[fast] != 0 ) swap (nums[slow++], nums[fast]); } }
先用快慢指针法处理字符串中的退格,最后比较两个处理完的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 bool backspaceCompare (string s, string t) { backspaceString (s); backspaceString (t); return s == t; }void backspaceString (string& s) { int slow = 0 ; for (int fast = 0 ; fast < s.size (); fast++) { if (s[fast] != '#' ) s[slow++] = s[fast]; else if (slow > 0 ) slow--; } s.resize (slow); }
左右指针
先平方后排序
时间复杂度:\(O(n+n\log{n})\approx O(n\log{n})\)
空间复杂度(不考虑返回新数组):\(O(\log{n})\) 的栈空间进行排序
双指针
时间复杂度:$ O(n) $
空间复杂度(不考虑返回新数组):$ O(1) $
因为数组本身是从小到大排序,平方后首尾指针往中间最小值递减。
所以可以使用两个指针分别为left
,right
:
新平方数组的从右往左,取两个指针的最大值,然后不断更新左右指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 vector<int > sortedSquares (vector<int >& nums) { int index = (int )nums.size (); vector<int > newNums (index) ; int left = 0 , right = index - 1 ; while (left <= right) { int sqrLeft = nums[left] * nums[left]; int sqrRight = nums[right] * nums[right]; if (sqrLeft > sqrRight) { newNums[--index] = sqrLeft; left++; } else { newNums[--index] = sqrRight; right--; } } return newNums; }
其他