位运算
#. 判断 是否为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
异或
-
任何数和 00 做异或运算,结果仍然是原来的数,即 a⊕0=a。
-
任何数和其自身做异或运算,结果是 00,即 a⊕a=0。
-
异或运算满足交换律和结合律,即 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) 。
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]
解释:2 与 7 之和等于目标数 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]
思路:慢指针 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 。不需要考虑数组中超出新长度后面的元素。
思路:当快指针遇到的值域当前慢指针指向的值不一样时,慢指针前进一位,并且替换成快指针此时指向的值。
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 中找到了,返回 true。
class 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 轴共同构成的容器可以容纳最多的水。
输入:[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 。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。
输入: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
方法一:推荐
思路:定义一个 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]
解释:
从索引 0 和 9 开始的子串分别是 "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"
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
思路:使用最少的硬币数=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… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。