【题号】1
【题目描述】
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
【常规解法】
一、暴力双循环
二、两遍哈希表
在第一遍对数组进行遍历时,按顺序存入哈希表。key为数组中的数值,value为该数值对应的下标。
在第二遍对数组进行遍历时,以(目标值-元素数值)查找是否有对应的key。若有,则输出数组遍历时元素数值对应的i、以及所找到key对应的value。
三、一遍哈希表
与方法二类似,不过在第一遍存入哈希表的过程中、同时进行key的查找。
【我的代码】
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
for i in range(0,len(nums)):
for j in range(i+1,len(nums)):
if target==nums[i]+nums[j]:
return [i,j]
"""
两层暴力循环(list)
"""
def twoSum(nums, target):
lens = len(nums)
j=-1
for i in range(lens):
if (target - nums[i]) in nums:
if (nums.count(target - nums[i]) == 1)&(target - nums[i] == nums[i]):#如果num2=num1,且nums中只出现了一次,说明找到是num1本身。
continue
else:
j = nums.index(target - nums[i],i+1) #index(x,i+1)是从num1后的序列后找num2
break
if j>0:
return [i,j]
else:
return []
"""
两层暴力循环(单向)
"""
def twoSum(nums, target):
lens = len(nums)
j=-1
for i in range(1,lens):
temp = nums[:i]
if (target - nums[i]) in temp:
j = temp.index(target - nums[i])
break
if j>=0:
return [j,i]
"""
两遍哈希表
"""
def twoSum(nums, target):
hashmap={}
for ind,num in enumerate(nums):
hashmap[num] = ind
for i,num in enumerate(nums):
j = hashmap.get(target - num)
if j is not None and i!=j:
return [i,j]
"""
一遍哈希表
"""
def twoSum(nums, target):
hashmap={}
for i,num in enumerate(nums):
if hashmap.get(target - num) is not None:
return [i,hashmap.get(target - num)]
hashmap[num] = i #这句不能放在if语句之前,解决list中有重复值或target-num=num的情况
作者:lao-la-rou-yue-jiao-yue-xiang
链接:https://leetcode-cn.com/problems/two-sum/solution/xiao-bai-pythonji-chong-jie-fa-by-lao-la-rou-yue-j/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
一、双循环暴力解法
我能想到的就是这种方法。不过思路和方法都可以参考一下范例。
范例1中用的不是直接判断两个num加起来是否为target,而是根据已知的num去查找所需要的num2是否在数组中。由于这里的数组在python中用了List表示,所以可以使用num2 in nums和**nums.index(num2)**快速查到num2是否存在及其下标号。
在范例1的基础上,对于已知的num,不从整个Nums list中去寻找Num2,而是只从[0:i]、即num元素前的部分List中寻找。
二、哈希表
哈希表的查找速度很快,所以考虑利用哈希表。
“通过以空间换取速度的方式,我们可以将查找时间从 O(n)O(n) 降低到 O(1)O(1)。哈希表正是为此目的而构建的,它支持以 近似 恒定的时间进行快速查找。”
把nums中每一个元素及其下标,分别作为key和value存入哈希表,后通过在哈希表查找所需要的num2,获得num2对应的下标Value。
这里有两种方法,第一种是把入表和查找完全分开,比较好理解;第二种是一边入表,一边在已入表的元素中查找,更为高效。
这里要注意的是对于第一个方法,先入表再查找,会发生重复元素在入表时的下标覆盖;对于第二种方法,需要先查找、再入表,否则对于有重复元素的数组、会发生覆盖。
【题号】26
【题目描述】
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
【常规解法】
双指针法。
一个慢指针,一个快指针 。
将快指针指向元素与慢指针指向元素进行比较。若相同,快指针进入下一位,慢指针不操作。若不同,快指针指向内容覆盖慢指针的下一位,快慢指针各自进位。直到慢指针到达数组边界。
【我的代码】
class Solution(object):
def removeDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
#单指针逐次剔除法
"""
i=1
while(i
#双指针逐次覆盖法
i=0 #慢指针
j=1 #快指针
lenNums=len(nums)
if lenNums<=1:
return lenNums
while(j<lenNums):
if nums[i]!=nums[j]:
i=i+1
if j!=i:
nums[i]=nums[j]
j=j+1
lenNewNums=i+1
nums=nums[0:lenNewNums]
return lenNewNums
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int i = 0;
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/solution/shan-chu-pai-xu-shu-zu-zhong-de-zhong-fu-xiang-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这道题我先用了比较普通的方法,遍历一遍元素,若该元素与上一元素相同,则剔除该元素。
参考范例的双指针法后,我又写了一个版本的代码。这个双指针法主要是两个指针,快指针遍历去检查元素,慢指针只有出现不相同的元素时才进位。
【题号】27
【题目描述】
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
【常规解法】
方法一:双指针
i为慢指针,j为快指针。起始时均指向数组首端。
当nums[j]不为目标值时,将nums[j]的值赋给nums[i],且i、j同时递增。
若nums[j]为目标值,则只有j递增。
最终返回的i即为移除元素后的数组长度。
方法二:双指针——改变数组顺序
i为慢指针,j为快指针。i起始指向数组末端,j指向首端。
当nums[j]不为目标值时,将nums[j]的值与nums[i]交换。i递减。
若nums[j]为目标值,i递增。
【我的代码】
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
i=0
j=0
while(j<len(nums)):
if nums[j]!=val:
nums[i]=nums[j]
i=i+1
j=j+1
return i
#双指针——正向慢指针
public int removeElement(int[] nums, int val) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != val) {
nums[i] = nums[j];
i++;
}
}
return i;
}
#双指针——反向慢指针
public int removeElement(int[] nums, int val) {
int i = 0;
int n = nums.length;
while (i < n) {
if (nums[i] == val) {
nums[i] = nums[n - 1];
// reduce array size by one
n--;
} else {
i++;
}
}
return n;
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/remove-element/solution/yi-chu-yuan-su-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这道题与上一道题十分相似,所以我就很自然得使用了双指针法。
我参考了上一题,快慢指针均从首端出发,若快指针的值不是目标删除值的话,则用快指针的值覆盖慢指针的值,最后利用慢指针的索引号确认新数组的长度。
由于这道题题干中给出了可以修改数组长度,所以双指针法还有另一种实现方式,将慢指针置于末端。若快指针的扫描中发现了目标值,则将快慢指针的值调换,且慢指针递减一位。
【题号】35
【题目描述】
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
【常规解法】
这题就是一道很经典的二分查找。
【我的代码】
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
beg=0
end=len(nums)-1
while (end-beg)>1:
mid=(beg+end)/2
if nums[mid]>target:
end=mid
elif nums[mid]==target:
return mid
else:
beg=mid
if(target>nums[end]):
return end+1
elif(target>nums[beg]):
return end
else:
return beg
【执行情况】
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
}
作者:guanpengchn
链接:https://leetcode-cn.com/problems/search-insert-position/solution/hua-jie-suan-fa-35-sou-suo-cha-ru-wei-zhi-by-guanp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这个二分算法其实很经典,思路清晰,但代码写的方式可能就会比较不同。再考虑到逻辑处理,可能就会比较繁琐。
这个范例提供了一个经典好用的二分算法写法,可以参考一下。
【题号】53
【题目描述】
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
【常规解法】
一、暴力法
二、动态规划法
建立一个与nums大小相同的数组d,其中d[i]存放着,以nums[i]作为最后一个元素的子列的最大值。
而nums[i]=max{(d[i-1]+nums[i]),nums[i]}
三、贪心法
按顺序计算顺序子序的sum值,当经过某个元素后sum值<0,则从下一个元素开始重新计算sum。
max从sum值中取最大。
四、分治法
最大子列存在位置只有三种,只存在于前半段,只存在于后半段,或者跨中间。
将原始序列从三个角度考虑,前半部分、后半部分和整体。最后的最大值取三个角度分别计算后最大值。
对于前半部分和后半部分,利用分治的思想,再将其作如原始序列一样的划分与计算。
对于整体部分,以中心为起点分别求,前半段和后半段的最大值子序列合值,并将其加起来,作为这个整体部分的最大值。
【我的代码】
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left=0
right=1
maxSum=nums[0]
currentSum=nums[0]
while(right<len(nums)):
currentSum=currentSum+nums[right]
tempLeft=left
tempSum=currentSum
while(tempLeft<right):
tempSum=tempSum-nums[tempLeft]
if tempSum>currentSum:
left=tempLeft+1
currentSum=tempSum
tempLeft=tempLeft+1
if currentSum>maxSum:
maxSum=currentSum
right=right+1
return maxSum
#动态规划
class Solution:
def maxSubArray(self, nums: 'List[int]') -> 'int':
n = len(nums)
max_sum = nums[0]
for i in range(1, n):
if nums[i - 1] > 0:
nums[i] += nums[i - 1]
max_sum = max(nums[i], max_sum)
return max_sum
#贪心
class Solution:
def maxSubArray(self, nums: 'List[int]') -> 'int':
n = len(nums)
curr_sum = max_sum = nums[0]
for i in range(1, n):
curr_sum = max(nums[i], curr_sum + nums[i])
max_sum = max(max_sum, curr_sum)
return max_sum
#分治
class Solution:
def cross_sum(self, nums, left, right, p):
if left == right:
return nums[left]
left_subsum = float('-inf')
curr_sum = 0
for i in range(p, left - 1, -1):
curr_sum += nums[i]
left_subsum = max(left_subsum, curr_sum)
right_subsum = float('-inf')
curr_sum = 0
for i in range(p + 1, right + 1):
curr_sum += nums[i]
right_subsum = max(right_subsum, curr_sum)
return left_subsum + right_subsum
def helper(self, nums, left, right):
if left == right:
return nums[left]
p = (left + right) // 2
left_sum = self.helper(nums, left, p)
right_sum = self.helper(nums, p + 1, right)
cross_sum = self.cross_sum(nums, left, right, p)
return max(left_sum, right_sum, cross_sum)
def maxSubArray(self, nums: 'List[int]') -> 'int':
return self.helper(nums, 0, len(nums) - 1)
【分析】
这道题我用的基本就是暴力做法了,时间开销特别大。
其中思路上最关键的一点偏差就是,我老去想,在对第i个元素进行考虑时候,同时去想第前i个元素中,是否会有让d[i]更小的情况发生。实际上,对第i个元素考虑时候,只需要去考虑d[i-1],而对d[i-1]考虑时候只要想着d[i-2]。要让每一个元素都已经获得了自己的最小子列了,这样他们的下一个元素只需要考虑自己以及前一个元素的最小子列的取舍关系了。这也是动态规划法的重点。
而贪心算法,与动态规划法有点类似,就按顺序去加下一个元素,取sum。如果这个sum值小于0了,说明前半段已经归零了,考虑后面的元素时候,就不需要考虑前半段了。
最后一个分治法。把最后可能的子列情况分为了三种,并在这三种中发现实际计算的只有“跨中间元素”这一种。而完全属于前半段和完全属于后半段,又可以分别再取分三种情况考虑。
【题号】66
【题目描述】
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
【常规解法】
第一个思路是,去判断“9”。有多少9就补多少0。再考虑第一个非9的位是加1还是增加位数补1。
第二个思路是,正常使用数学计算的思路去逐位加法,再考虑是否要进位。
【我的代码】
class Solution(object):
def plusOne(self, digits):
"""
:type digits: List[int]
:rtype: List[int]
"""
popCount=0
while digits and digits[-1]==9:
digits.pop()
popCount=popCount+1
if not digits:
digits.append(1)
else:
digits[-1]=digits[-1]+1
while popCount>0:
digits.append(0)
popCount=popCount-1
return digits
class Solution(object):
def plusOne(self, digits):
"""
:type digits: List[int]
:rtype: List[int]
"""
tosum=1
for i in range(len(digits)-1,-1,-1):
cursum=tosum+digits[i]
digits[i]=cursum%10
tosum=cursum//10
if tosum==0:
return digits
else:
return [tosum]+digits
【分析】
我使用了去倒序判断“9”,若为9再出栈。到了第一个非9位时候,根据栈是否为空,决定加1或补1。最后再入栈出栈次数的0。
范例使用了一个常规的数学思路,设定了一个进位器,去逐位计算新的值以及是否需要进位。由于题目要求加一,进位器存的值默认为1。若每一位全部计算完毕,且进位器里无值,则正常返回;若全部计算完毕,进位器还有值,则通过合并数组的方式来补1.(return [tosum]+digits)
【拓展】
python抖机灵小技巧,把数组转为字符串,字符串转为整型,整型便可直接加一。再将新的整型转为数组。
num=int(''.join(str(i) for i in digits))+1
return [int(i) for i in str(num)]
【题号】88
【题目描述】
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。
说明:
初始化 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]
【常规解法】
一、双指针 / 从前往后
最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。
由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m)O(m) 的空间复杂度。
二、双指针 / 从后往前
方法二已经取得了最优的时间复杂度O(n + m)O(n+m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。
从结尾开始改写 nums1 的值,不需要额外空间。
【我的代码】
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
i=0
j=0
while j<n:
setInNums1=False
while i<j+m:
if nums1[i]>nums2[j]:
nums1.insert(i,nums2[j])
nums1.pop()
i=i+1
setInNums1=True
break
i=i+1
if not setInNums1:
while i<n+m:
nums1[i]=nums2[j]
i=i+1
j=j+1
j=j+1
#从前往后
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: void Do not return anything, modify nums1 in-place instead.
"""
# Make a copy of nums1.
nums1_copy = nums1[:m]
nums1[:] = []
# Two get pointers for nums1_copy and nums2.
p1 = 0
p2 = 0
# Compare elements from nums1_copy and nums2
# and add the smallest one into nums1.
while p1 < m and p2 < n:
if nums1_copy[p1] < nums2[p2]:
nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
# if there are still elements to add
if p1 < m:
nums1[p1 + p2:] = nums1_copy[p1:]
if p2 < n:
nums1[p1 + p2:] = nums2[p2:]
作者:LeetCode
链接:https://leetcode-cn.com/problems/merge-sorted-array/solution/he-bing-liang-ge-you-xu-shu-zu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#从后往前
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: void Do not return anything, modify nums1 in-place instead.
"""
# two get pointers for nums1 and nums2
p1 = m - 1
p2 = n - 1
# set pointer for nums1
p = m + n - 1
# while there are still elements to compare
while p1 >= 0 and p2 >= 0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
# add missing elements from nums2
nums1[:p2 + 1] = nums2[:p2 + 1]
作者:LeetCode
链接:https://leetcode-cn.com/problems/merge-sorted-array/solution/he-bing-liang-ge-you-xu-shu-zu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
我的方法是两个数组各设一个指针,如果nums2所指向元素小于nums1所指向元素,那就将nums2元素插入nums1所指元素的位置。逐位往后踢。
参考的方法也是先在两个数组各设定一个指针,然后设定了一个比较器。**不断将指针指向元素放入比较器进行比较。可以设定比较器中较小元素依次放在nums1前端,或者较大元素放在nums1后端。**前者的话需要另外把原先nums1中元素存起来,后者不需要。
【拓展】
对于Python,可以直接将两个数组粗暴得拼在一起,然后直接sort了。
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: void Do not return anything, modify nums1 in-place instead.
"""
nums1[:] = sorted(nums1[:m] + nums2)
作者:LeetCode
链接:https://leetcode-cn.com/problems/merge-sorted-array/solution/he-bing-liang-ge-you-xu-shu-zu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【题号】118
【题目描述】
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
【常规解法】
利用动态规划的思想,先生成第1行元素,接着递归利用第i-1行的元素生成第i行的元素。
【我的代码】
class Solution(object):
def generate(self, numRows):
"""
:type numRows: int
:rtype: List[List[int]]
"""
triangle=[]
if numRows>0:
line=[]
line.append(1)
triangle.append(line)
for i in range(2,numRows+1):
line=[]
line.append(1)
for j in range(1,i-1):
line.append(triangle[-1][j-1]+triangle[-1][j])
line.append(1)
triangle.append(line)
return triangle
class Solution:
def generate(self, num_rows):
triangle = []
for row_num in range(num_rows):
# The first and last row elements are always 1.
row = [None for _ in range(row_num+1)]
row[0], row[-1] = 1, 1
# Each triangle element is equal to the sum of the elements
# above-and-to-the-left and above-and-to-the-right.
for j in range(1, len(row)-1):
row[j] = triangle[row_num-1][j-1] + triangle[row_num-1][j]
triangle.append(row)
return triangle
作者:LeetCode
链接:https://leetcode-cn.com/problems/pascals-triangle/solution/yang-hui-san-jiao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这一题就是很常规的动态规划。观察到第i行的生成与i-1行的内容密切相关。
由于每一行的内容都是对称的,所以算出半行就可以复制了。
【拓展】
观察一下规律,发现当前一行只比上一行多了一个元素,最最关键的一点:本行元素等于上一行元素往后错一位再逐个相加:
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
if numRows == 0: return []
res = [[1]]
while len(res) < numRows:
newRow = [a+b for a, b in zip([0]+res[-1], res[-1]+[0])]
res.append(newRow)
return res
作者:lu-cheng-5
链接:https://leetcode-cn.com/problems/pascals-triangle/solution/qu-qiao-jie-fa-cuo-yi-wei-zai-zhu-ge-xiang-jia-28m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【题号】121
【题目描述】
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
【常规解法】
一、贪心法
一次遍历数组,将当日价格与此时的历史最低价格做对比,若低则记为新的最低价格,若不是,则算出差值。
二、带哨兵元素的单调栈
在原数组末端补一个0的哨兵元素。建立栈底为最小值的单调栈,若入栈元素大于栈顶元素,则正常入栈;若入栈元素小于栈顶元素,则栈顶元素出栈,并计算栈顶元素与栈底元素的差值作为利润。哨兵元素保证了最终除了哨兵元素外所有元素均出栈,即所有元素都经过了利润的计算。
三、转换为最大连续子数组和的动态规划问题
由于a[j]-a[i]=(a[j]-a[j-1])+(a[j-1]-a[j-2])+……+(a[i+1]-a[i])
所以求a[j]-a[i]的最大值,实际上就是求数组[a-b for a,b in zip(prices[1:],prices[:len(prices)-1])]的最大连续子数组和。
此处的动态规划可以参考第53题。
【我的代码】
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if not prices:
return 0
profit=0
minPrice=int(1e9)
maxPrice=0
for price in prices:
if price<maxPrice and price>minPrice:
continue
if price<minPrice:
minPrice=price
maxPrice=price
elif price>maxPrice:
maxPrice=price
if (maxPrice-minPrice)>profit:
profit= maxPrice-minPrice
return profit
"""
贪心算法
"""
class Solution:
def maxProfit(self, prices: List[int]) -> int:
inf = int(1e9)
minprice = inf
maxprofit = 0
for price in prices:
maxprofit = max(price - minprice, maxprofit)
minprice = min(price, minprice)
return maxprofit
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/121-mai-mai-gu-piao-de-zui-jia-shi-ji-by-leetcode-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
"""
单调栈
"""
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0;
vector<int> St;
prices.emplace_back(-1); \\ 哨兵✈️
for (int i = 0; i < prices.size(); ++ i){
while (!St.empty() && St.back() > prices[i]){ \\ 维护单调栈
ans = std::max(ans, St.back() - St.front()); \\ 维护最大值
St.pop_back();
}
St.emplace_back(prices[i]);
}
return ans;
}
};
作者:wen-mu-yang
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/c-li-yong-shao-bing-wei-hu-yi-ge-dan-diao-zhan-tu-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
"""
转为最大子数组和后动态规划
"""
class Solution {
public:
int maxProfit(vector<int> &prices) {
if (prices.size() <= 1)
{
return 0;
}
int size = prices.size() - 1;
for (int i = 0; i < size; i++) {
prices[i] = prices[i + 1] - prices[i];
}
int res = maxSubArray(prices, size);
return (res > 0) ? res : 0;
}
int maxSubArray(vector<int> &nums, int right) {
int res = nums[0];
for (int i = 1; i < right; i++) {
if (nums[i - 1] > 0) {
nums[i] += nums[i - 1];
}
res = max(res, nums[i]);
}
return res;
}
};
作者:melo7
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/jiang-ti-mu-zhuan-huan-wei-zui-da-lian-xu-zi-shu-z/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这道题比较好想到的方法,以及我使用的方法就是贪心算法。依次遍历去算出当前价格与历史价格的差值作为利润。
增加哨兵的单调栈做法,本质上也是利用了单调栈去处理与历史最低价的关系。 单调栈的作用是:用O(n)O(n)的时间得知所有位置两边第一个比他大(或小)的数的位置。单调栈的使用场景:当你需要高效率查询某个位置左右两侧比他大(或小)的数的位置的时候。
最后是将问题转化为最大连续子数组和,这个入手点比较难想到,转换为这个问题后,就可以参考53题使用动态规划,即根据以前一个元素作为末尾元素的最大连续子数组,求出每个元素以结尾的最大连续子数组和。
【题号】122
【题目描述】
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
【常规解法】
一、暴力法
利用回溯的思想,将每一天分为操作(买或卖)和不操作两种状态去递归。
二、贪心算法
一遍递归的贪心算法,对于每一天都取使得局部利润最大化。
由于连续的买卖在数值上,与持有多天再买卖相同。所以可以将贪心进行到“每一天”,可以直接考虑p[i]-p[i-1]是否大于0,若大于0,则将差值算入总利润。虽然实际操作并不能每天实时买卖,但从结果上来看,局部的贪心结果等于全局的利润最大值。
但这个方法有一个缺陷,就是它并没有模拟实际的操作,所以当存在有手续费时,这个方法就失效了。
三、动态规划
动态规划的思想是:将多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。其中后阶段仅受前一阶段的影响。
分为以下几个步骤:1、定义状态;2、思考状态转移方程;3、确定起始;4、确定终止;5、考虑状态压缩。
1、定义状态
状态d[i][j]定义如下:表示第i天处于第j种状态时,所已获得的现金收益。j的取值有0和1,0代表不持有,1代表持有。
2、思考状态转移方程
d[i][j]的取值仅与d[i-1][j]有关。通俗得来讲,第i天不持有股票的现金情况,等于“第i-1天不持有股票的不操作,与第i-1天持有股票的操作(卖出)”的最大值。对于第i天持有股票的现金情况与上述类似,只不过操作改成了不持有股票时的买入。
3、确定起始
对于d[0][j],即第一天是否要操作,若第一天不操作,则现金情况仍为0;若第一天操作(买入),则第一天的现金情况的第一天股价的负数。起始值为d[0][0]=0;d[0][1]=-prices[0]。
4、确定终止
因为最后一步肯定是要变现的(手里有股票时候的现金 肯定小于手里没股票时候的现金)。所以最后输出d[len(prices)-1][0]。
5、考虑状态压缩
由于每一天的计算仅与前一天的计算有关,而且我们只需要最后一天的结果。所以可以不需要定义一个状态数组,定义两状态变量轮流用就行了。
【我的代码】
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
profit=0
cab=[]
for i in range(0,len(prices)-1):
if ( not cab ) and prices[i]<prices[i+1]:
cab.append(prices[i])
elif cab and prices[i]>prices[i+1]:
profit=profit+(prices[i]-cab.pop())
if cab:
profit=profit+(prices[len(prices)-1]-cab.pop())
return profit
"""
暴力法
"""
class Solution {
public int maxProfit(int[] prices) {
return calculate(prices, 0);
}
public int calculate(int prices[], int s) {
if (s >= prices.length)
return 0;
int max = 0;
for (int start = s; start < prices.length; start++) {
int maxprofit = 0;
for (int i = start + 1; i < prices.length; i++) {
if (prices[start] < prices[i]) {
int profit = calculate(prices, i + 1) + prices[i] - prices[start];
if (profit > maxprofit)
maxprofit = profit;
}
}
if (maxprofit > max)
max = maxprofit;
}
return max;
}
}
作者:LeetCode
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-ii-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
"""
贪心算法
"""
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit = 0
for i in range(1, len(prices)):
tmp = prices[i] - prices[i - 1]
if tmp > 0: profit += tmp
return profit
作者:jyd
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/best-time-to-buy-and-sell-stock-ii-zhuan-hua-fa-ji/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
"""
动态规划
"""
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
// 0:持有现金
// 1:持有股票
// 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
// 这两行调换顺序也是可以的
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[len - 1][0];
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/tan-xin-suan-fa-by-liweiwei1419-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
// cash:持有现金
// hold:持有股票
// 状态转移:cash → hold → cash → hold → cash → hold → cash
int cash = 0;
int hold = -prices[0];
int preCash = cash;
int preHold = hold;
for (int i = 1; i < len; i++) {
cash = Math.max(preCash, preHold + prices[i]);
hold = Math.max(preHold, preCash - prices[i]);
preCash = cash;
preHold = hold;
}
return cash;
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/tan-xin-suan-fa-by-liweiwei1419-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【分析】
这一题对应题目的特解用的是,峰谷法,我用的也是这个方法。通俗来说就是,买连续上涨的,在开始下落前卖掉。
这里我们考虑一些通解。分别是暴力法、贪心算法和动态规划法。
暴力法,主要是考虑到每一天都会有两个选项,操作或不操作,因此不断去递归。
贪心算法的话,不断去取局部最优值,最后化简成将每一天的正值收益累积起来。
一道题如果能用贪心算法的话,往往也可以使用动态规划法。这个方法是这道题的方法中最有深思价值的。其中最重要的是,根据状态的变换(每天根据是否操作会有不同的结构)去定义状态表达量,紧接着考虑状态转换方程,并定义起始值、终止值,甚至考虑状态压缩。
【题号】167
【题目描述】
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
【常规解法】
一、哈希表
遍历数组中的元素,遍历循环中先判断是否由以nums[i]作为索引的值,若无,则将{target-nums[i],i}作为新的元素存入哈希表,若有则打印出dic[nums[i]]+1和当前i+1。(即打印出nums中值为target-nums[i]的下标j再+1,和当前下标i再+1)
二、双指针
由于数据已经是有序的了,所以分别在数组的首尾两端设置指针。
若两指针所指元素之和小于目标值,则小指针进一位;若两指针所指元素之和大于目标值,则大指针退一位。
【我的代码】
class Solution(object):
def twoSum(self, numbers, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
dic={}
for i in range(0,len(numbers)):
temp=dic.get(numbers[i],-1)
if temp!=-1 and temp!=i:
return [temp+1,i+1]
dic[target-numbers[i]]=i
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int low = 0, high = numbers.size() - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target)
return {low + 1, high + 1};
else if (sum < target)
++low;
else
--high;
}
return {-1, -1};
}
};
【分析】
这一道题我比较直接用的是哈希表,时间复杂度O(n),空间复杂度O(n)。
由于本题数组已经是有序的了,所以可以用双指针,其时间复杂度也是O(n),但空间复杂度只有O(1)。
我一开始不敢用双指针的原因,是害怕在指针移动的过程中,漏过了正确的组合。但实际上,由于数组有序,所以不会产生漏判。至于为什么,就与双指针的原理有关了:双指针本质上是缩减搜索空间。
当target>num[0]+num[7]时,就可以判定i=0这一行都不会存在正确答案,从而压缩搜索空间至剩下几行。在指针层面则表现为i进位。
【拓展】
通过判断nums[j]与target的大小,可以缩小右指针范围。