估算算法的时间复杂度的时候一般低于10^7 次操作每秒为佳 sort 的时间复杂度与编译器具体实现相关,简单考虑成快速排序nlogn
哈希 1.两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。示例 1:
输入:nums = [2,7,11,15], target = 9
输出:**[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:**[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
**进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : vector<int > twoSum (vector<int >& nums, int target) { unordered_map<int ,int > map; for (size_t i = 0 ; i < nums.size (); i++){ if (map.contains (target-nums[i])){ vector<int > ret; ret.push_back (map[target-nums[i]]); ret.push_back (i); return ret; } map[nums[i]] = i; } return vector <int >(); } };
2.字母异位词分组 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
示例 1: 输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 输出: [[“bat”],[“nat”,”tan”],[“ate”,”eat”,”tea”]] 解释: 在 strs 中没有字符串可以通过重新排列来形成 “bat”。 字符串 “nat” 和 “tan” 是字母异位词,因为它们可以重新排列以形成彼此。 字符串 “ate” ,”eat” 和 “tea” 是字母异位词,因为它们可以重新排列以形成彼此。
示例 2: 输入: strs = [“”] 输出: [[“”]] 示例 3: 输入: strs = [“a”] 输出: [[“a”]]
提示: 1 <= strs.length <= 104 0 <= strs[i].length <= 100 strs[i] 仅包含小写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector<vector<string>> groupAnagrams (vector<string>& strs) { unordered_map<string,vector<string>> group_strs; for (auto & s : strs){ string sorted_s = s; ranges::sort (sorted_s); group_strs[sorted_s].push_back (s); } vector<vector<string>> ans; ans.reserve (group_strs.size ()); for (auto & [_,value] : group_strs){ ans.push_back (value); } return ans; } };
3.最长连续序列 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1: 输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2: 输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9
示例 3: 输入:nums = [1,0,1,2] 输出:3
提示: 0 <= nums.length <= 105 -109 <= nums[i] <= 109
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 39 40 41 42 43 44 45 46 47 class Solution {public : int longestConsecutive (vector<int >& nums) { if (nums.size () < 2 ) return nums.size (); sort (nums.begin (), nums.end ()); int ans = 1 ; int start = 0 ; int cur = 1 ; for (int i = 1 ; i < nums.size (); i++) { if (nums[i] == nums[i - 1 ]) continue ; if (nums[i] - nums[i - 1 ] == 1 ) { cur++; ans = max (cur, ans); } else { start = i; cur = 1 ; } } return ans; } int longestConsecutive (vector<int >& nums) { unordered_set<int > num_set; for (const int & num : nums){ num_set.insert (num); } int longestStreak = 0 ; for (auto & num : num_set){ if (!num_set.count (num - 1 )){ int currentNum = num; int currentStreak = 1 ; while ( num_set.count (currentNum + 1 )){ currentNum += 1 ; currentStreak += 1 ; } longestStreak = max (longestStreak,currentStreak); } } return longestStreak; } };
双指针 4.移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1: 输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0]
示例 2: 输入: nums = [0] 输出: [0]
提示: 1 <= nums.length <= 104 -231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : void moveZeroes (vector<int >& nums) { int n = nums.size (),left = 0 , right = 0 ; while (right < n){ if (nums[right]){ swap (nums[left],nums[right]); left++; } right++; } } };
5.盛水最多的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明:你不能倾斜容器。
示例 1: 输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2: 输入:height = [1,1] 输出:1
提示: n == height.length 2 <= n <= 105 0 <= height[i] <= 104
题解:https://leetcode.cn/problems/container-with-most-water/solutions/207215/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked 关键: 接水量=两个指针指向的数字中较小值∗指针之间的距离 双指针中靠时,后者减少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int maxArea (vector<int >& height) { int low = 0 , n = height.size (),high = n-1 ; int max_container = 0 ; while (low < high){ int container = ( high - low ) * min (height[low],height[high]); max_container = max (max_container,container); if (height[low] < height[high]){ low++; }else { high--; } } return max_container; } };
6.三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
示例 1: 输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2: 输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3: 输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示: 3 <= nums.length <= 3000 -105 <= nums[i] <= 105
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 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { vector<vector<int >> ans; sort (nums.begin (),nums.end ()); for (int i = 0 ; i < nums.size () - 2 ; i++ ){ if (i > 0 && nums[i] == nums[i-1 ]){ continue ; } if (nums[i] > 0 ){ break ; } int l = i + 1 , r = nums.size ()-1 ; while (l < r ){ int sum = nums[l]+nums[r]+nums[i]; if (sum < 0 ){ l++; }else if (sum > 0 ){ r--; }else { ans.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--; } } } return ans; } };
6.接雨水 定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1: 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2: 输入:height = [4,2,0,3,2,5] 输出:9
提示: n == height.length 1 <= n <= 2 * 104 0 <= height[i] <= 105
题解
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Solution {public : int trap (vector<int >& height) { int total_sum = 0 ; vector<int > dp_l_max; vector<int > dp_r_max; dp_l_max.reserve (height.size ()); dp_r_max.reserve (height.size ()); dp_l_max[0 ] = 0 ; int n = height.size (); dp_r_max[n-1 ] = 0 ; for (int i = 0 ; i < height.size () - 1 ; i++) { dp_l_max[i+1 ] = max (dp_l_max[i],height[i]); dp_r_max[n-i-2 ] = max (dp_r_max[n-1 -i],height[n-i-1 ]); } for (int i = 0 ; i < height.size (); i++){ int max_l = dp_l_max[i], max_r = dp_r_max[i]; if (max_r > height[i] && max_l > height[i]){ total_sum += min (max_r,max_l) - height[i]; } } return total_sum; } int trap (vector<int >& height) { int ans = 0 ; stack<int > stk; int n = height.size (); for (int i = 0 ; i < n; ++i) { while (!stk.empty () && height[i] > height[stk.top ()]) { int top = stk.top (); stk.pop (); if (stk.empty ()) { break ; } int left = stk.top (); int currWidth = i - left - 1 ; int currHeight = min (height[left], height[i]) - height[top]; ans += currWidth * currHeight; } stk.push (i); } return ans; } int trap (vector<int >& height) { int n = height.size (); if (n<=2 ) return 0 ; int l=0 ,r=n-1 ; int leftmax=height[l], rightmax=height[r]; int res=0 ; while (l<r) { if (leftmax < rightmax) { res += leftmax-height[l]; l++; leftmax = max (height[l], leftmax); } else { res += rightmax-height[r]; r--; rightmax = max (height[r], rightmax); } } return res; } };
滑动窗口
1.区分是固定长度的窗口还是非固定长度的窗口,非固定长度窗口参照7.的模板 2.字符串比较的不考虑顺序时可以采用字典集的方式降低时间复杂度 3.如果字符串中包含负数,如第9题,可能导致窗口伸缩状态不确定,这种情况滑动窗口不适用
7. 无重复字符的最长子串 给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1: 输入: s = “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。注意 “bca” 和 “cab” 也是正确答案。
示例 2: 输入: s = “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3: 输入: s = “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。
提示: 0 <= s.length <= 5 * 104 s 由英文字母、数字、符号和空格组成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int lengthOfLongestSubstring (string s) { unordered_set<char > char_sets; int maxlength = 0 ; int curlength = 0 ; for (int l = 0 , r = 0 ; r < s.length ();r++){ while (l < r && char_sets.contains (s[r])){ char_sets.erase (s[l]); l++; curlength -- ; } char_sets.insert (s[r]); curlength++; maxlength = max (maxlength,curlength); } return maxlength; } };
8.找到字符串中所有字母异位词 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 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 <= s.length, p.length <= 3 * 104 s 和 p 仅包含小写字母
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 class Solution {public : vector<int > findAnagrams (string s, string p) { int sLen = s.size (), pLen = p.size (); if (sLen < pLen) { return vector <int >(); } vector<int > ans; vector<int > sCount (26 ) ; vector<int > pCount (26 ) ; for (int i = 0 ; i < pLen; ++i) { ++sCount[s[i] - 'a' ]; ++pCount[p[i] - 'a' ]; } if (sCount == pCount) { ans.emplace_back (0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { --sCount[s[i] - 'a' ]; ++sCount[s[i + pLen] - 'a' ]; if (sCount == pCount) { ans.emplace_back (i + 1 ); } } return ans; } };
字符串 9.和为 k 的子数组 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。
示例 1: 输入:nums = [1,1,1], k = 2 输出:2
示例 2: 输入:nums = [1,2,3], k = 3 输出:2
提示: 1 <= nums.length <= 2 * 104 -1000 <= nums[i] <= 1000 -107 <= k <= 107
前缀合+哈希表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int subarraySum (vector<int >& nums, int k) { int ans = 0 ; unordered_map<int ,int > prefix_sum_count_map; prefix_sum_count_map[0 ] = 1 ; int cur_prefix_sum = 0 ; for (int i = 0 ; i < nums.size ();i++){ cur_prefix_sum += nums[i]; if (prefix_sum_count_map.contains (cur_prefix_sum - k)){ ans += prefix_sum_count_map[cur_prefix_sum-k]; } if (prefix_sum_count_map.contains (cur_prefix_sum)){ prefix_sum_count_map[cur_prefix_sum]++; }else { prefix_sum_count_map[cur_prefix_sum] = 1 ; } } return ans; } };
10.滑动窗口中的最大值 给你一个整数数组 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
示例 2: 输入:nums = [1], k = 1 输出:[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 35 36 37 38 39 class Solution {public : string minWindow (string s, string t) { unordered_map<char ,int > need_map,cur_wind_map; for (auto & c : t){ need_map[c]++; } int r= 0 , l = 0 ; int valid = 0 ; int minLen = INT_MAX, start = 0 ; for (;r < s.size ();r++){ if (need_map.contains (s[r])){ cur_wind_map[s[r]]++; if (cur_wind_map[s[r]] == need_map[s[r]]){ valid++; } } while (valid == need_map.size ()){ if (r - l < minLen){ minLen = r + 1 - l; start = l; } if (need_map.contains (s[l])){ if (cur_wind_map[s[l]] == need_map[s[l]]){ valid--; } cur_wind_map[s[l]]--; } l++; } } if (minLen == INT_MAX){ return "" ; } return s.substr (start,minLen); } };
普通数组 11.最大子数组和 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。
示例 1: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2: 输入:nums = [1] 输出:1
示例 3: 输入:nums = [5,4,-1,7,8] 输出:23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int maxSubArray (vector<int >& nums) { vector<int > dp; dp.reserve (nums.size ()); dp[0 ] = nums[0 ]; int max_sum = dp[0 ]; for (int i = 1 ; i < nums.size (); i++){ dp[i] = max (dp[i-1 ]+nums[i],nums[i]); max_sum = max (max_sum,dp[i]); } return max_sum; } };
12.合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1: 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2: 输入:intervals = [[1,4],[4,5]] 输出:[[1,5]] 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
示例 3: 输入:intervals = [[4,7],[1,4]] 输出:[[1,7]] 解释:区间 [1,4] 和 [4,7] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104 intervals[i].length == 2 0 <= starti <= endi <= 104
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector<vector<int >> merge (vector<vector<int >>& intervals) { if (intervals.size () == 0 ){ return {}; } sort (intervals.begin (),intervals.end ()); vector<vector<int >> merged; for (int i = 0 ; i < intervals.size () ; i++){ int l = intervals[i][0 ], r = intervals[i][1 ]; if (merged.empty ()){ merged.push_back ({l,r}); }else { if (merged.back ()[1 ] < l){ merged.push_back ({l,r}); }else { merged.back ()[1 ] = max (merged.back ()[1 ],r); } } } return merged; } };
13.轮转数组 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2: 输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]
提示: 1 <= nums.length <= 105 -231 <= nums[i] <= 231 - 1 0 <= k <= 105
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : void reverse (vector<int >& nums ,int start,int end) { while (start < end){ swap (nums[start],nums[end]); start++; end--; } } void rotate (vector<int >& nums, int k) { k %= nums.size (); reverse (nums,0 ,nums.size ()-1 ); reverse (nums,0 ,k-1 ); reverse (nums,k,nums.size ()-1 ); } };