diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/binarySearch.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/binarySearch.py" new file mode 100644 index 0000000000000000000000000000000000000000..dccb6cb6397f3a9564d266e8cd7eba229fd01345 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/binarySearch.py" @@ -0,0 +1,32 @@ +# 二分查找 +# 顺序查找,不排序可以进行; +# 二分查找,不排序,不可以;必须先排序,再进行查找 +from Everyday.E001 import bubbleSort +def binarySearch(iList,key): + left=0 + right=len(iList)-1 + while (right-left)>1: + mid = (left+right)//2 + if keyiList[mid]: + left=mid + else: + return mid + + if key==iList[left]: + return left + elif key==iList[right]: + return right + else: + return -1 + +if __name__=="__main__": + iList=[7,3,5,1,9,4] + print("原来:") + print(iList) + bubbleSort(iList) + + key=int(input("请输入列表中的一个整数:")) + print("the index is: ") + print(binarySearch(iList,key)) \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/bubbleSort.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/bubbleSort.py" new file mode 100644 index 0000000000000000000000000000000000000000..24368fd8df93f8f27fae222303f5bf4dacb3f7f0 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/bubbleSort.py" @@ -0,0 +1,18 @@ +# 把最大的那个数移动到最后边 +def bubbleSort(iList): + lenlist=len(iList) + if lenlist<=1: + return iList + + for i in range(1,lenlist): + for j in range(0,lenlist-i): + if iList[j+1] int: + @functools.lru_cache(amount) + def dp(rem) -> int: + if rem < 0: return -1 + if rem == 0: return 0 + mini = int(1e9) + for coin in self.coins: + res = dp(rem - coin) + if res >= 0 and res < mini: + mini = res + 1 + return mini if mini < int(1e9) else -1 + + self.coins = coins + if amount < 1: return 0 + return dp(amount) + +if __name__=="__main__": + coins = [1, 2, 5] + amount = 11 + print("coins and amount is: ",coins,amount) + n=Solution() + res=n.coinChange(coins,amount) + print(res,'个硬币,至少') \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/drop_duplicates.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/drop_duplicates.py" new file mode 100644 index 0000000000000000000000000000000000000000..8bb85bc2d9d964032597b579ee19c120ab0c043f --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/drop_duplicates.py" @@ -0,0 +1,28 @@ +def drop_duplicates(nums): + """ + 移除数组中的重复元素 + - 直接对nums进行修改 + :param nums: + :return: + """ + # 指示当前需要判断重复的元素的索引 + slow = 1 + + # 用来遍历数组, 查找与slow值不同的元素 + # 如果找到了, slow的值将被赋值为fast代表的值 + fast = 1 + + while fast < len(nums): + if nums[fast-1] != nums[fast]: + nums[slow] = nums[fast] + slow += 1 + fast += 1 + return nums + +if __name__=="__main__": + ilist=[7, 3, 3, 1, 9, 4] + print('before:',ilist) + + print('after: ',drop_duplicates(ilist)) + + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/fib.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/fib.py" new file mode 100644 index 0000000000000000000000000000000000000000..c707c31add03b508a425e80b677d0edbae4ab963 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/fib.py" @@ -0,0 +1,17 @@ +def fib(n): + """ + 0,1,1,2,3,5 + 肩上两数之和 + 1--fib(n)=fib(n-1)+fib(n-2) + 2--初始值的考虑 + :param n: + :return: + """ + if n<2: + return n + return fib(n-1)+fib(n-2) + +if __name__=="__main__": + for i in range(0,10): + print(fib(i)) + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/has_circle.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/has_circle.py" new file mode 100644 index 0000000000000000000000000000000000000000..1e53064624064cc39a0b080d39c2d3c01f2cbd8c --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/has_circle.py" @@ -0,0 +1,67 @@ +# 链表是否是环形链表 +# fast和slow +#判断是否是回文链表 +# [3,2,1,4,0] pos=1; 尾节点0指向2 true + +def hasCycle(head): + fast=head + slow=head + if head==None: + return False + while fast: + try: + fast = fast.next.next + slow = slow.next + except: + return False + if fast == None or slow == None: #不是环形 + return False + if fast==slow: #是环形 + return True + + + +class ListNode:#节点类 + def __init__(self,x): + self.val=x + self.next=None +import random +def CreateChain(n):#创建链表 + print("调用CreateChain(n):",end=":") + # randomList=random.sample(range(20),n) + randomList=[1,2,3,4,5,6] + print("randonList is:%s",str(randomList)) + pointer=ListNode(randomList.pop(0)) + head=pointer + #开始追加元素 + while randomList: + pointer.next=ListNode(randomList.pop(0)) + pointer=pointer.next + pointer.next=head.next.next.next + return head + +def showChain(head):#输出链表 + print("调用showChain(head):",end=":") + if head==None: + print("the chain is None") + return None + m=0 + while head.next and (m<6): + print("%d ->"%head.val,end="") + head=head.next + m+=1 + # print() + print("%d"%head.val) + return + +if __name__=="__main__": + head1 = CreateChain(6) + showChain(head1) + + print("hascycle:") + print(hasCycle(head1)) + # pass + + + + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/maxprofit.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/maxprofit.py" new file mode 100644 index 0000000000000000000000000000000000000000..d66cfd0c74a99627aa0428513996083ddb6bfbbb --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/maxprofit.py" @@ -0,0 +1,39 @@ +# 假设把某股票的价格按照时间先后顺序存储在数组中, +# 请问买卖该股票一次可能获得的最大利润是多少? + +# 时间复杂度0(n),使用了py的切片,只需要进行一次循环遍历; +# [5, 5, 5, 1, 35, 15] +# 34 +# [7, 1, 5, 3, 6, 4] +# 5 +# [7, 6, 4, 3, 1] +# 0 +# [9, 11, 8, 5, 7, 12, 16, 14] +# 11 + + +def maxProfit(prices): #一次循环,o(n)时间复杂度 + if len(prices)<2: + return 0 + profit=0 + for i in range(len(prices)-1): + sub=max(prices[i+1:])-prices[i] + profit=max(sub,profit) + return profit + +if __name__=="__main__": + price=[5,5,5,1,35,15] + print(price) + print(maxProfit(price)) + + price1 = [7,1,5,3,6,4] + print(price1) + print(maxProfit(price1)) + + price2 = [7,6,4,3,1] + print(price2) + print(maxProfit(price2)) + + price3 = [9, 11, 8, 5, 7,12,16,14] + print(price3) + print(maxProfit(price3)) diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/mergeSort.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/mergeSort.py" new file mode 100644 index 0000000000000000000000000000000000000000..a4089527edb7557362dc4a67b9404ea2018cba27 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/mergeSort.py" @@ -0,0 +1,29 @@ +import math + +def mergeSort(arr): + import math + if(len(arr)<2): + return arr + middle = math.floor(len(arr)/2) + left, right = arr[0:middle], arr[middle:] + return merge(mergeSort(left), mergeSort(right)) + +def merge(left,right): + result = [] + while left and right: + if left[0] <= right[0]: + result.append(left.pop(0)) + else: + result.append(right.pop(0)) + while left: + result.append(left.pop(0)) + while right: + result.append(right.pop(0)) + return result + +if __name__=="__main__": + iList = [7, 3, 5, 1, 9, 4] + print('iList is',iList) + res=mergeSort(iList) + print('after:',res) + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/quickSort.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/quickSort.py" new file mode 100644 index 0000000000000000000000000000000000000000..f8bc049fcf15231763ff09188193bcbf8d562e59 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/quickSort.py" @@ -0,0 +1,31 @@ +def quickSort(arr, left=None, right=None): + left = 0 if not isinstance(left,(int, float)) else left + right = len(arr)-1 if not isinstance(right,(int, float)) else right + + if left < right: + partitionIndex = partition(arr, left, right) + quickSort(arr, left, partitionIndex-1)#操作左边 + quickSort(arr, partitionIndex+1, right)#操作右边 + return arr + +# 分割为两部分 +def partition(arr, left, right): + pivot = left + index = pivot+1 + i = index + while i <= right: + if arr[i] < arr[pivot]: + arr[i],arr[index]=arr[index],arr[i] + index+=1 + i+=1 + + arr[pivot],arr[index-1]=arr[index-1],arr[pivot] + return index-1 + + + +if __name__=="__main__": + iList = [7, 3, 5, 1, 9, 4] + print('iList is',iList) + res=quickSort(iList) + print('after:',res) diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/remove_dup.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/remove_dup.py" new file mode 100644 index 0000000000000000000000000000000000000000..2f0ff24debde2f8aedfc721371fc90c83eda16cb --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/remove_dup.py" @@ -0,0 +1,32 @@ +# 删除重复的元素,只有一个是重复的,其余的都是出1遍 +# 若是相等,就左移动,就将其放置在最后一个,返回n +# before: +# [7, 7, 3, 3, 1, 1, 4, 5] +# after: +# [7, 3, 1, 4, 5] +def removeDuplicates(nums): + n=len(set(nums)) + i=0 + while i 强调两个指针相对的位置 + + - 二分查找 + - 两数之和 + - 链表翻转 + - 滑动窗口 + +- 快慢指针 + + > 强调两个指针移动速度, 或者叫步长 + + - 判断链表中是否有环 + - 链表中点 + - 数组去重 + - 快排 + +## 题集 + +- 快慢指针 + + - [删除有序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) + + ```python + def drop_duplicates(nums): + """ + 移除数组中的重复元素 + - 直接对nums进行修改 + :param nums: + :return: + """ + # 指示当前需要判断重复的元素的索引 + slow = 1 + + # 用来遍历数组, 查找与slow值不同的元素 + # 如果找到了, slow的值将被赋值为fast代表的值 + fast = 1 + + while fast < len(nums): + if nums[fast-1] != nums[fast]: + nums[slow] = nums[fast] + slow += 1 + fast += 1 + return nums, slow + ``` + + - [环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) + + ```python + def has_cycle(head): + slow = fast = head + + # 如果最终fast为null, 说明当前到达链表的末端 + # 说明当前链表没有环 + while fast: + fast = fast.next.next + slow = slow.next + if slow == fast: + return True + return False + ``` + + - [环形链表2](https://leetcode-cn.com/problems/linked-list-cycle-ii/) + + ```python + def detect_cycle(head): + """ + slow = a + b + fast = a + b + c + b + + fast = 2slow + + a + b + c + b = 2a + 2b + => c = a + :param head: + :return: + """ + slow = fast = head + while fast: + fast = fast.next.next + slow = slow.next + if slow == fast: + # 停止的位置就是快慢指针相遇点 + break + + # 当前无环 + if not fast: + return None + + slow = head + while slow != fast: + # 因为a=c, slow和fast以同样的速度前进, 相交点就是入环点 + slow = slow.next + fast = fast.next + return slow + ``` + +- 左右指针 + + - [验证回文串](https://leetcode-cn.com/problems/valid-palindrome/) + + ```python + def is_palindrome(string): + left = 0 + right = len(string) - 1 + while right >= left: + while not string[left].isalpha(): + left += 1 + + while not string[right].isalpha(): + right -= 1 + + if string[left].lower() == string[right].lower(): + left += 1 + right -= 1 + else: + print("break", left, right) + break + + return left-1 == right+1 or left-1 == right + ``` + + - [合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) + + 略 + + - [二分查找](https://leetcode-cn.com/problems/binary-search/) + + ```python + def binary_search(nums, target): + left = 0 + right = len(nums) - 1 + + while left <= right: + mid = (left + right) // 2 + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + 1 + elif nums[mid] > target: + right = mid - 1 + + return -1 + ``` + +## 练习 + +- 理解双指针 +- 练习课上讲的习题 \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\346\216\222\345\272\217\347\256\227\346\263\225-23-1noteyun .md" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\346\216\222\345\272\217\347\256\227\346\263\225-23-1noteyun .md" new file mode 100644 index 0000000000000000000000000000000000000000..5e8c12a7ae3aea9a6b3cdefbaa994a7a96d5b308 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\346\216\222\345\272\217\347\256\227\346\263\225-23-1noteyun .md" @@ -0,0 +1,270 @@ +23-1-noteyun + +## 时间复杂度 + +在日常生活中, 我们描述物体的重量使用的是kg, 描述物体的长度使用的是m, 那么相对的, 在计算机科学中也需要一种度量来定性地**描述算法的运行时间**, 这种度量方法称为**大O表示法**. + +- 声明`f(n)`作为我们的函数, `n`表示的参数. 不同的参数会导致算法运行的时间不同. 那么最坏的情况就是`T(n)` + +- `T(n) = O(f(n))`, `f(n)`表示表达式执行的次数之和. + + ```python + def foo(n): + for i in range(n): + 表达式 + f(n) = n + T(n) = O(n) + 该算法的时间复杂度为O(n) + ``` + + - 键对值取值 + + ``` + O(1) + ``` + + - 嵌套的for循环 + + ```python + for i in range(n): + for j in range(m): + 表达式 + + O(n*m) + ``` + + - 执行多次的for循环 + + ```python + for i in range(n): + 表达式 + for j in range(n): + 表达式 + + O(2n) + ``` + + 上述代码时间复杂度为O(2n), 但是在大O表示法当中, 可以忽略系数和常数, 所以当前的时间复杂度也可以写为O(n) + + - 二分法(共8个小球,有一个小球的质量偏重,二分法,找到该小球) + + ```python + 2**3 = 8 + 3 = log8 + f(n) = logn + + O(logn) + 冒选插 归快计 + ``` + +## 常见的排序 + +``` +https://www.runoob.com/w3cnote/selection-sort.html +``` + +- 选择排序 +- 冒泡排序 +- 插入排序 +- 归并排序 +- 快速排序 +- 桶排序 + +## 选择排序O(n²) + +- 从当前**元素后**的无序元素数组中找到最小值 + +- 如果找到了, 将该元素与当前元素交换 + +- 如果没找到, 说明当前元素是后续无序数组中的最小值 + +- 执行上述过程`n-1`次 + + ```python + def selection_sort(array): + # 最后一个元素无序比较, 所以执行n-1次 + for i in range(len(array)-1): + min_index = i + # 从当前index+1的位置开始遍历 + for j in range(i+1, len(array)): + if array[j] < array[min_index]: + min_index = j + + # 如果找到最小值 + if i != min_index: + array[i], array[min_index] = array[min_index], array[i] + ``` + + ```python + + # 选择排序,选择最小的那个,放在合适的位置 + def selectionSort(iList): + if len(iList)<=1: + return iList + for i in range(0,len(iList)-1): + + if (iList[i]!=(min(iList[i:]))):#这个是不是最小的那个 + minIndex=iList.index(min(iList[i:])) + iList[i],iList[minIndex]=iList[minIndex],iList[i] + print("第%d轮结果为:"%(i+1)) + print(iList) + return iList + + if __name__=="__main__": + iList=[7,3,5,1,9,4] + print("原来:") + print(iList) + print("~~~~~~~~~~~~") + print("排序后") + print(selectionSort(iList)) + + ``` + + + +## 冒泡排序O(n²) + +- 本质就是将最大值移动到数组末端 + +- 比较cur和next, 如果cur > next, 则交换两者 + +- 执行上述过程`n`次 + + ```python + def bubble_sort(array): + for i in range(len(array)): + # 每完成一次遍历, 结尾就有一个最大元素, 那么我们接下来的遍历过程可以-1 + for j in range(len(array)-1-i): + # array[j]表示cur + if array[j] > array[j+1]: + array[j], array[j+1] = array[j+1], array[j] + ``` + + ```python + # 冒泡排序,将最大的数放在最后位置 + def bubbleSort(iList): + if len(iList)<=1: + return iList + for i in range(1,len(iList)): + for j in range(0,len(iList)-i):#有j+1,请注意j的取值范围 + if iList[j+1] 无序数组[a, b, c, d, e] 可以拆分成[a, b], [c, d, e] + > + > [a, b] 排序后得到[b, a] + > + > [c, d, e]可以拆分为[c, d], [e] + > + > [c, d] 排序后得到[d, c] + > + > 得到了三组有序数组 + > + > [d, c], [e] 归并后得到[d, c, e] + > + > [d, c, e], [b, a]归并后得到[d, b, c, a, e] + +## 快速排序 + +1.先确定一个基准数,让后按照比较规则,如本例是升序排列,则将比基数大的放到右边,比基数小的放到左边。 + +2.接下来各边重复步骤1,直到全部排序完毕。 + +```python +def quickSort(arr, left=None, right=None): + left = 0 if not isinstance(left,(int, float)) else left + right = len(arr)-1 if not isinstance(right,(int, float)) else right + if left < right: + partitionIndex = partition(arr, left, right) + quickSort(arr, left, partitionIndex-1) + quickSort(arr, partitionIndex+1, right) + return arr + +# 分割为两部分 +def partition(arr, left, right): + pivot = left + index = pivot+1 + i = index + while i <= right: + if arr[i] < arr[pivot]: + swap(arr, i, index) + index+=1 + i+=1 + swap(arr,pivot,index-1)#交换 + return index-1 + +# 交换两元素 +def swap(arr, i, j): + arr[i], arr[j] = arr[j], arr[i] + +if __name__=="__main__": + iList = [7, 3, 5, 1, 9, 4] + print('iList is',iList) + res=quickSort(iList) + print('after:',res) + +``` + + + +## 练习 + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\347\256\227\346\263\225\351\242\230\351\233\206-\344\271\260\345\215\226\350\202\241\347\245\250\346\234\200\344\275\263\346\227\266\346\234\272-\347\241\254\345\270\201\344\270\252\346\225\26023-2noteyun.md" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\347\256\227\346\263\225\351\242\230\351\233\206-\344\271\260\345\215\226\350\202\241\347\245\250\346\234\200\344\275\263\346\227\266\346\234\272-\347\241\254\345\270\201\344\270\252\346\225\26023-2noteyun.md" new file mode 100644 index 0000000000000000000000000000000000000000..698e715ceb5e45699eacb5f256948a1f5be7272b --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week23/\347\256\227\346\263\225\351\242\230\351\233\206-\344\271\260\345\215\226\350\202\241\347\245\250\346\234\200\344\275\263\346\227\266\346\234\272-\347\241\254\345\270\201\344\270\252\346\225\26023-2noteyun.md" @@ -0,0 +1,239 @@ +23-2-noteyun + + + +## 动态规划 + +``` +#百度:动态规划 +https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/529408?fr=aladdin +``` + +**动态规划**(Dynamic Programming,DP)是运筹学的一个分支,是求解[决策过程](https://baike.baidu.com/item/决策过程/6714639)最优化的过程. 在**多阶段决策**问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起**状态的转移**,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法. + +简单地说, 动态规划的本质就是穷**举所有的可能性, 寻找最优解.** + +- 动态规划的应用 + + **一切求最值的情况, 都可以通过动态规划来解决.** + + - 资金管理问题 + - 资源分配问题 + - 最短路径问题 + - 最优决策问题 + +- 动态规划的思想 + + - 通过分治法来实现动态规划决策的过程 + - 将待解决的问题分解成若干个子问题, **先求解子问题**. + - 动态规划会产生大量的重复计算. + - 动态规划中当前状态取决于**上一阶段状态**和**本阶段的状态** + - 基于2, 3两点. 所以动态规划使用的是**数组来做为缓存数据结构**, 而不是hashmap, 该数组被称位dp_table(dynamic programming table) + +- 动态规划的解题步骤 + + - 列出状态转移方程 + + ```python + # 暂时简单地理解为可以用来表达任意子问题的方程式 + dp_table[n] = f(dp_table[n-1]) + ``` + + - 初始化长度为n的`dp_table` + + - 分治思想最终会达到拆无可拆的地步, 该情形下就是dp_table的初始值 + + > 在斐波那契函数当中, 索引值小于2就是拆无可拆的地步 + + - 穷举所有决策和**记录产生的结果/状态** + + - dp_table最终会被填满, 即所有的可能性已经穷举完毕 + - **一般来说**, dp_table[-1]就是我们要求的最值 + +## 题集 + +- 斐波那契函数 + + 斐波那契函数严格来说不属于动态规划, 因为它不必求最值. 这里我们只用它体现将问题变成求解子问题的过程 + + ```python + def fib(n): + """ + 斐波那契函数当前值等于前两个值之和 + 0, 1, 1, 2, 3, 5 + + 1. 写出状态转移方程 fib(n) = fib(n-1) + fib(n-2) + 2. 考虑最终子问题的初始值 + :param n: + :return: + """ + if n < 2: + return n + + return fib(n-1) + fib(n-2) + ``` + +- [爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) + + ```python + def upstairs(n): + """ + 台阶1: [1] + 台阶2: [1 + 1], [0 + 2] + 台阶3: ([1 + 1 + 1], [0 + 2 + 1]), ([1 + 2]) + 台阶4: ([1 + 1 + 1 + 1], [0 + 2 + 1 + 1], [1 + 2 + 1]), ([1 + 1 + 2], [0 + 2 + 2]) + + dp_table[n] = dp_table[n-1] + dp_table[n-2] + :param n: + :return: + """ + dp_table = [0] * n + + # 初始化dp_table + dp_table[0], dp_table[1] = 1, 2 + + # 填满dp_table, 遍历n即可 + for i in range(2, n): + dp_table[i] = dp_table[i-1] + dp_table[i-2] + + return dp_table[-1] + ``` + +- [买股票的最佳时机1](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) + + ```python + def max_profit(prices): + """ + - 只有两次操作, 一次为买, 一次为卖 + - 最大利润 = 最高位 - 最低位 + + 股票问题的难点在于要考虑所谓的状态不仅仅表示上一个状态的值(也就是利润的最大值) + 还要考虑股票的状态, 因为股票买了之后持有是有价值的, 但是暂时不能算作利润. + 最终利润 = 持有的股票 - 当前股价. 也点出了股票的本质就是未来的期望 + 卖 买 + dp_table = [[最大利润, 最低股价的股票]...] + :param prices: + :return: + """ + dp_table = [[0, 0] for _ in range(len(prices))] + + dp_table[0] = [0, 7] + for i in range(1, len(prices)): + # 最大利润 = max(prev最大利润, cur股价 - prv持有股价) + dp_table[i][0] = max(dp_table[i-1][0], prices[i] - dp_table[i-1][1]) + # 持有股票 = min(prev持有股价, cur股价) + dp_table[i][1] = min(dp_table[i-1][1], prices[i]) + return dp_table + ``` + + ```python + # 假设把某股票的价格按照时间先后顺序存储在数组中, + # 请问买卖该股票一次可能获得的最大利润是多少? + + # 时间复杂度0(n),使用了py的切片,只需要进行一次循环遍历; + # [5, 5, 5, 1, 35, 15] + # 34 + # [7, 1, 5, 3, 6, 4] + # 5 + # [7, 6, 4, 3, 1] + # 0 + # [9, 11, 8, 5, 7, 12, 16, 14] + # 11 + + + def maxProfit(prices): #一次循环,o(n)时间复杂度 + if len(prices)<2: + return 0 + profit=0 + for i in range(len(prices)-1): + sub=max(prices[i+1:])-prices[i] + profit=max(sub,profit) + return profit + + if __name__=="__main__": + price=[5,5,5,1,35,15] + print(price) + print(maxProfit(price)) + + price1 = [7,1,5,3,6,4] + print(price1) + print(maxProfit(price1)) + + price2 = [7,6,4,3,1] + print(price2) + print(maxProfit(price2)) + + price3 = [9, 11, 8, 5, 7,12,16,14] + print(price3) + print(maxProfit(price3)) + + ``` + + + +- [买股票的最佳时机2](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) + + ```python + def max_profit2(prices): + """ + - 在不重复购买的情况下, 可以任意操作 + - 最大利润 = 交易操作的累计和 + + 这道题和上一道题的区别在于, 因为是多次操作, 那我们要寻找的不是最低价的股票 + 而是要得到每次买卖操作的最大利润. + 同样的, 持有的股票不能算作当前利润, 因为股票终究是要卖出才能得到最大收益. + 所以要分两种情况: + 持有股票: 最大利润 = 交易所得利润(同时持有股票) + 不持有股票: 最大利润 = 交易所得利润 + 上一阶段如果持有股票 - 当前股价 + + 卖 买 + dp_table = [[不持有, 持有]...] + :param prices: + :return: + """ + dp_table = [[0, 0] for _ in range(len(prices))] + + dp_table[0] = [0, -prices[0]] + + for i in range(1, len(prices)): + # 不持有的情况: 1. 上一次没有持有, 不存在卖的操作; 2. 上一次持有, 这次卖了 + dp_table[i][0] = max(dp_table[i-1][0], dp_table[i-1][1] + prices[i]) + # 持有的情况: 1. 上一次持有, 不存在买的操作; 2. 上一次不持有, 这次买了 + dp_table[i][1] = max(dp_table[i-1][1], dp_table[i-1][0] - prices[i]) + return dp_table[-1][0] + ``` + +## 练习 + +- 理解动态规划 +- 练习课堂讲解的题 +- 扩展: 完成题目 [凑零钱](https://leetcode-cn.com/problems/coin-change/) + +```python +import functools +class Solution: + def coinChange(self, coins, amount: int) -> int: + @functools.lru_cache(amount) + def dp(rem) -> int: + if rem < 0: return -1 + if rem == 0: return 0 + mini = int(1e9) + for coin in self.coins: + res = dp(rem - coin) + if res >= 0 and res < mini: + mini = res + 1 + return mini if mini < int(1e9) else -1 + + self.coins = coins + if amount < 1: return 0 + return dp(amount) + +if __name__=="__main__": + coins = [1, 2, 5] + amount = 11 + print("coins and amount is: ",coins,amount) + n=Solution() + res=n.coinChange(coins,amount) + print(res,'个硬币,至少') +``` +