贪心算法是一种常用的算法设计策略,旨在通过局部最优选择来构建全局最优解。它的基本思想是:在每一步选择中,都选择当前看起来最优的选项,而不考虑后续的影响。贪心算法通常用于解决最优化问题,尤其是在某些特定条件下能够得到全局最优解的问题
455. 分发饼干 - 力扣(LeetCode)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。输入: g = [1,2,3], s = [1,1]: 输出: 1
贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干:
class Solution {
public:
int findContentChildren(vector& g, vector& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int j=0;
for(int i=0;i=g[j])//满足条件的饼干,才能让饥饿度后移
{
j++;
}
}
return j;
}
};
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
n=len(g)
i=0
for x in s:
if i=g[i]:
i+=1
return i
135. 分发糖果 - 力扣(LeetCode)
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果: 每个孩子至少分配到 1 个糖果。 相邻两个孩子评分更高的孩子会获得更多的糖果。请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
肯定需要遍历,但是一次遍历考虑左右有些复杂化,可以从左到右和从右到左遍历,都是O(N)
class Solution {
public:
int candy(vector& ratings) {
int size = ratings.size();
if (size < 2) {
return size;
}
vector num(size, 1);
for (int i = 1; i < size; ++i) {
if (ratings[i] > ratings[i - 1]) {
num[i] = num[i - 1] + 1;
}
}
for (int i = size - 1; i > 0; --i) {
if (ratings[i] < ratings[i - 1])
//在第一次遍历的时候也许已经满足条件要考虑相等,所以用max
num[i - 1] = max(num[i - 1], num[i] + 1);
}
return accumulate(num.begin(), num.end(), 0);
}
};
class Solution:
def candy(self, ratings: List[int]) -> int:
n = len(ratings)
if n == 0: return 0
candy_nums = [1] * n
for i in range(1, n):
if ratings[i] > ratings[i - 1]:
candy_nums[i] = candy_nums[i - 1] + 1
for i in range(n - 1, 0, -1):
if ratings[i - 1] > ratings[i]:
candy_nums[i - 1] = max(candy_nums[i - 1], candy_nums[i] + 1)
return sum(candy_nums)
435. 无重叠区间 - 力扣(LeetCode)
给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠,在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此采取的贪心策略为,优先保留结尾小且不相交的区间。
class Solution {
public:
int eraseOverlapIntervals(vector>& intervals) {
sort(intervals.begin(),intervals.end(),[](vectora, vectorb){return a[1]intervals[i][0]){
res++;
}else{
j=i;
}
}
return res;
}
};
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x:x[1])
n=len(intervals)
if n<=1:
return 0
j=0
sum=0
for i in range(1,n):
if intervals[j][1]>intervals[i][0]:
sum+=1
else:
j=i
return sum
双指针是一种常用的算法技巧,双指针技术通常涉及两个指针在同一数据结构上移动,以达到特定的目的。特别适用于处理数组或链表等线性结构的问题,通过使用两个指针,可以有效地减少时间复杂度,简化代码逻辑。
左右指针:两个指针分别从数组的两端向中间移动。
快慢指针:一个指针移动较快,另一个指针移动较慢,常用于链表中查找中间节点或检测环。
滑动窗口:用于处理子数组或子串问题,动态调整指针以满足特定条件。
常见应用场景
查找特定元素:如在排序数组中查找两个数的和。
反转字符串或数组:通过左右指针交换元素。
合并两个有序数组:使用两个指针遍历两个数组。
滑动窗口问题:如最长无重复子串、最小覆盖子串等。
167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
使用左右指针,左指针从数组的开始位置,右指针从数组的结束位置,逐步逼近
class Solution {
public:
vector twoSum(vector& numbers, int target) {
int l=0,r=numbers.size()-1,sum;
while (l{l+1,r+1};
}
};
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
l,r=0,len(numbers)-1
while ltarget:
r-=1
else:
l+=1
return l+1,r+1
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序排列。
数组合并到nums1中,两数组都是非递减的,两个指针指向两个数组末尾,第三个指针指向nums1需要填充的位置。
88. 合并两个有序数组 - 力扣(LeetCode)
class Solution {
public:
void merge(vector& nums1, int m, vector& nums2, int n) {
int pos=m-- +n-- -1;
while (m>=0&&n>=0)
{
if(nums1[m]>nums2[n]){
nums1[pos--]=nums1[m--];
}else{
nums1[pos--]=nums2[n--];
}
//如果 nums2 的数字已经复制完,剩余nums1 的数字不需要改变,因为它们已经被排好序
}
while (n>=0)
{//nums2 的数字有可能没有处理完
nums1[pos--]=nums2[n--];
}
}
};
释义:滑动窗口的核心思想是使用两个指针(通常称为左指针和右指针)来表示一个窗口的范围。随着右指针的移动,窗口逐渐扩大;当满足某些条件时,左指针也会移动,从而缩小窗口。这个过程可以在一次遍历中完成,因此时间复杂度通常为 O(n)。
滑动窗口的类型:1)固定大小窗口:窗口的大小是固定的,适用于需要计算固定长度子数组的情况。2)动态大小窗口:窗口的大小是动态变化的,适用于需要满足特定条件的子数组或子串。
应用场景:1)最长无重复子串:找出字符串中最长的无重复字符的子串。2)最小覆盖子串:在一个字符串中找到包含另一个字符串所有字符的最小子串。3)固定大小的子数组:计算固定大小窗口的和、平均值等。4)子数组的最大/最小值:在给定范围内查找最大或最小值。
使用动态大小窗口,维护一个字符集合,动态调整左右指针
3. 无重复字符的最长子串 - 力扣(LeetCode)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map res;
int len=0;
int left=0;
int right=0;
while(right
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans=left=0
winds=set()
for right,c in enumerate(s):
while c in winds:
winds.remove(s[left])
left+=1
winds.add(c)
ans=max(ans,right-left+1)
return ans
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
76. 最小覆盖子串 - 力扣(LeetCode)
class Solution {
public:
string minWindow(string s, string t) {
vector chars(128,0);
vector flag(128,false);
for(int i=0;i=0){
++cnt;
}
while(cnt==t.size()){
if(r-l+10)){
--cnt;
}
++l;
}
}
}
return mini_size>s.size()?"":s.substr(min_l,mini_size);
}
};