文章目录
贪心算法解决问题(python实现)
当数据规模较大,暴力求解会导致超时,我们考虑想到一些更优的算法求解,即找规律。
1. 种花问题
思路一
计算空地的可种花数
- 如果是左右边界,连续两个0即可种一朵花;
- 如果在中间,0的个数为奇数,种花数为(num-1)//2;
- 如果在中间,0的个数为偶数,种花数为num//2-1;
- 将各个位置的种花数量累加,即得到总的可种花数;
- 总可种花数 >= 给定种花数,则为True;反之,则为False。
代码实现
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
count = 0
sign = True
sum = 0
if len(flowerbed) == 1:
if flowerbed[0] == 0:
sum = 1
elif 1 not in flowerbed:
sum = (len(flowerbed)+1)//2
else:
if flowerbed[0] == 0 and flowerbed[1] == 0: # 两个0在开头需要再加1
sum += 1
if flowerbed[-1] == 0 and flowerbed[-2] == 0 and len(flowerbed) > 2: # 两个0在结尾需要再加1
sum += 1
for i in range(len(flowerbed)):
if flowerbed[i] == 0:
count += 1 # 1与1之间有多时少个0
elif count > 0:
sum += self.flower(count) # 计算0在中间时的种花数,并累加
count = 0
if count > 0:
sum += self.flower(count)
if sum < n:
sign = False
return sign
def flower(self, count):
if count%2==1:
return (count-1)//2
else:
return count//2-1
思路二
将给定的花束种到花坛
- 如果是左右边界,出现两个0,n-1
- 如果是在中间,出现三个0,n-1
- 最后,n < 0,则返回False;反之,则为True
代码实现
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
for i in range(len(flowerbed)):
if i == 0:
left = 0 # # 对左边界的特殊处理
else:
flowerbed[i-1]
if i == len(flowerbed)-1:
right = 0
else:
flowerbed[i+1] # 对右边界的特殊处理
if flowerbed[i] == 0 and left == 0 and right == 0: # 当前节点为空,且左右为空的情况下
flowerbed[i] = 1 # 在当前节点种花
n -= 1 # 剩余花朵减一
return n <= 0
2. 最长回文串
思路
其实就是一个对称的问题,判断字符串能否写成左右对称的形式。
- 如果每个字符的数量都是偶数,那么回文串的长度即为原字符串长度;
- 如果有的字符数量是奇数,由于对称的中间元素不要求对称,因此回文串的长度为各个字符数量整除2再乘2,最后加1。
代码实现
class Solution:
def longestPalindrome(self, s: str) -> int:
num = set(s) # 用集合,判断字符串有几种元素
dic = {} # 用键值对记录不同元素的数量
for i in num:
dic[i] = 0
for i in range(len(s)): # 遍历一次传入的字符串,得到每个元素的数量
dic[s[i]] += 1
sum = 0 # 计算回文串长度
for i in num:
sum += dic[i]//2 # 对不同的字典值的1/2求和(够不够对称)
if sum*2 < len(s):
return sum*2+1 # 中间元素可以不必对称,如果有剩余元素,字符串长度可以加1
else:
return sum*2 # 所有元素数量刚好都是偶数的情况
代码优化
- 遍历字符串直接用for i in s, 得到的i即为字符 代码第4行
- 判断键或值存在于字典中,i in static.keys(), count in static.values() 第 5、7行
- divmod函数可以直接得到除数和余数 代码第8行
class Solution:
def longestPalindrome(self, s: str) -> int:
static = {}
for i in s:
if i in static.keys():
static[i] = static[i] + 1
else:
static[i] = 1
remain, total = 0, 0
for count in static.values():
total_, remain_ = divmod(count, 2)
remain += remain_
total += total_
if remain == 0:
return total * 2 + 0
else:
return total * 2 + 1
3. 买卖股票的最佳时机
思想
- 遍历字符串,min_index存储所过之处股票最低时的数组下标;
- 计算min_index时买入股票,当前位置卖股票时的利润(后面的-前面的),如果大于当前最大利润,则更新最大利润的值。
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit, min_index = 0, 0
for i in range(len(prices)):
if prices[i] < prices[min_index]: # 当前股票的最低价格
min_index = i
elif prices[i] - prices[min_index] > profit: # 当前卖可以获得更大利润
profit = prices[i] - prices[min_index]
return profit
4. 盛最多水的容器
思路1
让height数组中的每个元素依次为高度,然后计算宽度,最后得到面积
- 计算面积时分为左面和右面两部分,如果是边界,则左面或右面面积为0;
- 中间时候,如果左面或者右面有大于或者当前高度的,则这一侧存在面积;否则,这一侧面积为0。
代码实现
class Solution:
def maxArea(self, height: List[int]) -> int:
max_v = 0
for i in range(len(height)):
j = 0
while j < i and height[j] < height[i]: # 找到左面第一个大于或者等于当前高度的
j += 1
left_v = (i-j)*height[i]
k = len(height)-1
while k > i and height[k] < height[i]: # 找到右面第一个大于或者等于当前高度的
k -= 1
right_v = (k-i)*height[i]
if left_v + right_v > max_v:
max_v = left_v + right_v
return max_v
思路2
从两侧向中间移动,我们只需要移动较矮的一侧就可以
两侧即宽度最大,那么要想面积增大,宽度缩小时,只能增加高度。简单来说,如果高度增加了,宽度即使缩小,是不是面积也可能增大?
怎么让高度增加:短的那边移动。
代码实现
class Solution:
def maxArea(self, height: List[int]) -> int:
i, j, v = 0, len(height) - 1, 0
while i < j:
if height[i] < height[j]:
v = max(v, height[i] * (j - i))
i += 1 # 左面矮,移动左面
else:
v = max(v, height[j] * (j - i))
j -= 1 # 右面矮,移动右面
return v
5. 划分字母区间
思路1
遍历两次字符串,第一次统计字符数量,第二次遍历得到字母区间
- 第一次遍历字符串,用字典记录每个字符的数量;
- 复制字典,第二次遍历字符串,其中每访问字符一次,对应的字典值减1;
- 当某个字符数量为0时,判断其它字符是否在前面出现过,如果没出现过,当前位置即可划分;反之,寻找下一次字符数量为0。
代码实现
class Solution:
def partitionLabels(self, s: str) -> List[int]:
unit = sorted(set(s), key = s.index) # 字符串所有包含的元素
dic = {} # 用字典存元素和对应的个数。键是元素,值是元素个数
for i in unit:
dic[i] = 0
for i in range(len(s)):
dic[s[i]] += 1 # 遍历字符串,并计算每个元素的个数
cop_dic = {}
for i in unit:
cop_dic[i] = dic[i] # 复制一份字典
num = [] # 最后的返回值
k = -1 # 记录当前遍历的字符串位置
for i in range(len(s)):
dic[s[i]] -= 1 # 每访问一次,值-1
if dic[s[i]] == 0:
unit.remove(s[i]) # 字符串后面没有某个元素了,从集合中删除
sign = True
for j in unit:
if dic[j] != cop_dic[j]: # 判断其余元素的个数,是否前面没出现过
sign = False
if sign:
num.append(i-k) # 集合中剩余的元素都没出现过,可以从这里划分字符串
k = i # 当前访问的字符串位置
return num
思路2
通过找到相同字符串包含的区间,然后合并成不相交的区间
- 字符串只访问一次,为了创建一个字典{key1:[start,end], key2:[start,end],…};
- 读写一个列表为[[start, end], [start, end],…] 每个元素为一个字母区间的start, end。
- 怎么创建这个列表?我们已经得到了每个字符的起止位置,如果上一个元素的结束位置之前有其他元素开始了,(代码倒数第5行)那么我们需要合并这些元素的终止位置。
代码实现
class Solution:
def partitionLabels(self, s: str) -> List[int]:
index = {}
for i in range(len(s)): # 字符串只访问一次,为了创建一个字典{key1:[start,end], key2:[start,end],....}
if s[i] in index.keys():
index[s[i]][1] = i
else:
index[s[i]] = [i, i] # key存元素,value存[起始位置, 终止位置]
answer = [] # [[start, end], [start, end],...] 每个元素为一个字母区间的start, end
key_list = list(index.keys()) # 元素组成的列表,为了迭代enumerate产生 i 和 key
for i, key in enumerate(key_list): # 遍历index字典,更新answer
if len(answer) == 0:
answer.append(index[key]) # answer = [[i, j]] 字符串中第一个元素的起止位置
# 上一个元素的 结束 位置 > 当前元素的 开始 位置 ≈ 当前字母区间不只有一个元素
elif answer[-1][1] > index[key][0]:
# 上一个元素的结束位置为,现在和之前所有元素的结束位置,即max
answer[-1][1] = max(index[key][1], answer[-1][1])
else:
# 混合元素的字符串已经全部结束,开启了新一轮的字母区间,将新一轮的第一个元素位置加入列表
answer.append(index[key])
return [i[1] - i[0] + 1 for i in answer] # answer里面的每个元素 end-start+1