面試題——算法與數據結構Python實現

快速排序

參考:快速排序partition過程常見的兩種寫法+快速排序非遞歸實現

import random
def partition(arr, low, high):
    r=random.randint(low,high) # 隨機選[low,high]內的數與arr[low]交換
    arr[r],arr[low]=arr[low],arr[r]
    pivot=arr[low]
    while low < high:
        while low < high and arr[high] >= pivot:
        	high-=1
        arr[low] = arr[high]#從後面開始找到第一個小於pivot的元素,放到low位置
        while low < high and arr[low] <= pivot:
        	low+=1
        arr[high] = arr[low]#從前面開始找到第一個大於pivot的元素,放到high位置
    arr[low] = pivot#最後樞紐元放到low的位置
    return low

def quick_sort(arr, low, high):
    if low < high:
        mid = partition(arr, low, high)
        quick_sort(arr, low, mid-1)
        quick_sort(arr, mid+1, high)

歸併排序

參考:python歸併排序–遞歸實現

def merge(left, right):
    """合併兩個已排序好的列表,產生一個新的已排序好的列表"""
    result = []  # 新的已排序好的列表
    i = 0  # 下標
    j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result += left[i:]
    result += right[j:]
    return result

def merge_sort(seq):
    if len(seq) <= 1:
        return seq
    mid = len(seq) // 2  # 將列表分成更小的兩個列表
    # 分別對左右兩個列表進行處理,分別返回兩個排序好的列表
    left = merge_sort(seq[:mid])
    right = merge_sort(seq[mid:])
    # 對排序好的兩個列表合併,產生一個新的排序好的列表
    return merge(left, right)

冒泡排序

def bubble_sort(arr):
    n = len(arr)
    # 進行n次冒泡,每次排好一個
    for i in range(n):
        # 最後 i 個已經有序
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1] :
                arr[j], arr[j+1] = arr[j+1], arr[j]

二分查找

def binary_search(array,t):
    low = 0
    high = len(array)-1
    while low <= high:
        mid = (low+high)//2
        if array[mid] < t:
            low = mid + 1
       elif array[mid] > t:
            high = mid - 1
        else:
            return mid
    return -1

二分查找的拓展——上下界與個數問題

1、二分查找求上下界
2、二分查找求上界和下界

注意點:

1、(low+high)/2 容易溢出
2、注意邊界條件即各個while中的不等號處理

leetcode題目鏈接:LC34. Find First and Last Position of Element in Sorted Array

另外兩個關於二分查找的題目:

153. Find Minimum in Rotated Sorted Array

33. Search in Rotated Sorted Array

'''
 利用二分查找求出插入位置,在該位置插入後可以保持序列仍然有序
 bs_lower_bound返回的是可插入的最小位置
 bs_upper_bound返回的是可插入的最大位置
 換句話說,當元素存在且有多個時,返回值還有另外的含義:
 bs_lower_bound返回其第一次出現的位置
 bs_upper_bound返回其最後一次出現的位置 + 1
'''

def bs_lower_bound(arr, t):
    low = 0
    high = len(arr)  # 注意high初始化
    while low < high:  # 注意沒有等號
        mid = (high - low) // 2 + low
        if arr[mid] >= t:
            high = mid  # 求下界則把等號放到上邊界對應的分支
        else:
            low = mid + 1
    return low
    # 如果要在找不到該數字時返回-1而不是它可以插入的最低位置,則使用下述返回代碼
    return low if 0<=low<len(nums) and nums[low]==tar else -1


def bs_upper_bound(arr, t):
    low = 0
    high = len(arr)  # 注意high初始化
    while low < high:  # 注意沒有等號
        mid = (high - low) // 2 + low
        if arr[mid] > t:
            high = mid
        else:
            low = mid + 1  # 求上界則把等號放到下邊界對應的分支
    return low
    # 如果要在找不到該數字時返回-1而不是它可以插入的最高位置,則使用下述返回代碼
    return low if 0<low<=len(nums) and nums[low-1]==tar else -1



def searchRange(nums, target):
	"""
	在nums中查找target的最左出現位置和最右出現位置,如果不存在則返回[-1,-1]
	"""
    if len(nums) == 0:
        return [-1, -1]
    low = bs_lower_bound(nums, target)
    # bs_high找到的位置是可插入的最小位置,就等於元素出現的最左位置;如果等於len(nums)說明不存在
    if low>=len(nums) or nums[low] != target:
        low = -1
    # bs_high找到的位置是可插入的最大位置,減一纔是元素出現的最右位置;如果等於0說明不存在
    high = bs_upper_bound(nums, target) - 1  #注意減一
    if high<0 or nums[high] != target:
        high = -1
    return [low, high]

nums = [1, 1, 3, 3, 3, 5, 5, 5]

print("nums: ", nums)
print("index:", list(range(len(nums))))
print('= ' * 10)
print('lower bound of 0: ', bs_lower_bound(nums, 0))
print('lower bound of 1: ', bs_lower_bound(nums, 1))
print('lower bound of 2: ', bs_lower_bound(nums, 2))
print('lower bound of 3: ', bs_lower_bound(nums, 3))
print('lower bound of 4: ', bs_lower_bound(nums, 4))
print('lower bound of 5: ', bs_lower_bound(nums, 5))
print('lower bound of 6: ', bs_lower_bound(nums, 6))
print('= ' * 10)
print('upper bound of 0: ', bs_upper_bound(nums, 0))
print('upper bound of 1: ', bs_upper_bound(nums, 1))
print('upper bound of 2: ', bs_upper_bound(nums, 2))
print('upper bound of 3: ', bs_upper_bound(nums, 3))
print('upper bound of 4: ', bs_upper_bound(nums, 4))
print('upper bound of 5: ', bs_upper_bound(nums, 5))
print('upper bound of 6: ', bs_upper_bound(nums, 6))


'''
輸出:
nums:  [1, 1, 3, 3, 3, 5, 5, 5]
index: [0, 1, 2, 3, 4, 5, 6, 7]
= = = = = = = = = = 
lower bound of 0:  0
lower bound of 1:  0
lower bound of 2:  2
lower bound of 3:  2
lower bound of 4:  5
lower bound of 5:  5
lower bound of 6:  8
= = = = = = = = = = 
upper bound of 0:  0
upper bound of 1:  2
upper bound of 2:  2
upper bound of 3:  5
upper bound of 4:  5
upper bound of 5:  8
upper bound of 6:  8
'''

最小生成樹

TODO

線段樹和樹狀數組的異同

TODO

兩個棧模擬隊列和雙端隊列

棧 s1, s2 實現隊列
()入隊操作:
	將元素壓入s1

()出隊操作:
	if s2不爲空:
		彈出棧頂元素
	elif s1不爲空:
		將s1的元素逐個倒入s2,但最後一個元素彈出後直接返回而不用壓入s2
	else:
		拋出隊列爲空的異常

雙端隊列:

棧 s1, s2 實現雙端隊列
後入隊操作:
	將元素壓入s1
前入隊操作:
	將元素壓入s2

前出隊操作:
	if s2不爲空:
		彈出棧頂元素
	elif s1不爲空:
		將s1的元素逐個倒入s2,但最後一個元素彈出後直接返回而不用壓入s2
	else:
		拋出隊列爲空的異常
後出隊操作:
	if s1不爲空:
		彈出棧頂元素
	elif s2不爲空:
		將s2的元素逐個倒入s1,但最後一個元素彈出後直接返回而不用壓入s1
	else:
		拋出隊列爲空的異常

最長上升子序列,dp實現,nlogn實現

[1] LeetCode動態規劃題目總結(持續更新中)

[2] 最長遞增子序列LIS的O(nlogn)的求法

dp實現見資料[1]

nlogn實現,解釋見[2],python代碼實現如下:

class Solution:
    def lengthOfLIS(self, nums):
        if len(nums)==0:
            return 0
        tails=[nums[0]]  # 第一個數直接加入
        for i in range(1,len(nums)):  # 遍歷一遍其餘數
            x=nums[i]
            if x > tails[-1]:  # 如果大於tails中的所有數則直接加入(注意tails是升序的)
                tails.append(x)
            else:  # 否則二分查找出下界,然後*替換*掉該位置的元素
                low,high=0,len(tails)
                while low<high:
                    mid=low+(high-low)//2
                    if tails[mid]>=x:
                        high=mid
                    else:
                        low=mid+1
                tails[low]=x  # 注意找到下界後是替換而不是insert
        return len(tails)

快排時間複雜度和空間複雜度

最好情況是選中的pivot爲中值,因此劃分均衡

最壞情況是選中的pivot爲最小值或最大值,導致每次劃分爲1個和 剩餘

若選取pivot的方法是直接用low或high位置的元素,則最壞情況=數組有序,避免方法是隨機選取pivot

時間複雜度

平均情況下是 O(nlogn)O(n\log n),最壞情況下(數組有序)是 O(n2)O(n^2)

空間複雜度

快排的實現是遞歸調用的, 而且每次函數調用中只使用了常數的空間,因此空間複雜度等於遞歸深度,在和平均情況下爲 O(logn)O(\log n),爲O(n)O(n)

布隆過濾器知道嗎?用在什麼場景下?推導會麼(加分項)

詳解布隆過濾器的原理,使用場景和注意事項

布隆過濾器-維基百科

K個鏈表歸併

題目:https://leetcode.com/problems/merge-k-sorted-lists/submissions/

參考:https://leetcode.com/problems/merge-k-sorted-lists/discuss/10511/10-line-python-solution-with-priority-queue

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

import queue
class Solution(object):
    def mergeKLists(self, lists):
        pq=queue.PriorityQueue()
        # 需要定義__lt__函數用於定義何爲“更小”,這裏使用結點的值來定義小頂堆
        # 將True和False調換即可定義爲結點值的大頂堆
        ListNode.__lt__=lambda x,y: True if x.val<y.val else False
        for node in lists:
            if node is not None:
                pq.put(node)
        dummy=ListNode(None)
        curr=dummy
        while pq.qsize()>0:
            curr.next=pq.get()
            curr=curr.next
            if curr.next is not None:
                pq.put(curr.next)
        return dummy.next
        

二叉樹前序中序遍歷,重建二叉樹

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if inorder:
            ind = inorder.index(preorder.pop(0))
            root = TreeNode(inorder[ind])
            root.left = self.buildTree(preorder, inorder[0:ind])
            root.right = self.buildTree(preorder, inorder[ind+1:])
            return root

一般的實現是將preorder劃分兩部分給左右子樹的構建,但是這裏用pop()彈出了結點,而且是先對左子樹構建,所以到了右子樹的時候,preorder中左子樹的所有結點剛好pop完了

注意:若給前序和中序,直接問後序遍歷的結果,可以先建樹,然後再後序遍歷。

二叉樹的前序遍歷和中序遍歷的非遞歸實現

題目地址:

https://leetcode.com/problems/binary-tree-inorder-traversal/

https://leetcode.com/problems/binary-tree-preorder-traversal/

參考:https://blog.csdn.net/Applying/article/details/84982712

前序遍歷

# 二叉樹結點定義見前面的《二叉樹前序中序遍歷,重建二叉樹 》
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        if root is None:
            return res
        stack = []
        stack.append(root)
        while len(stack)!=0:
            p = stack.pop()
            # 訪問,即加入結果list
            res.append(p.val)
            # 這裏注意,要先壓入右子節點,再壓入左節點
            if p.right is not None:
                stack.append(p.right)
            if p.left is not None:
                stack.append(p.left)
        return res
        
# 二叉樹結點定義見前面的《二叉樹前序中序遍歷,重建二叉樹 》
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res=[]
        if root is None:
            return res
        
        stack = []
        p = root
        # 棧非空 或 p非空
        while len(stack)!=0 or p is not None:
            # 向左搜索,尋找最左的節點,即中序遍歷的第一個節點
            while p is not None:
                stack.append(p)
                p = p.left
            # 如果棧非空則訪問當前節點並往右走一步表示接下來訪問其右子樹
            if len(stack)!=0:   
                r = stack.pop()
                res.append(r.val)
                p = r.right
        return res

哈希表相關

解決哈希衝突:

  • 開放定址法
    • 線性探測法
    • 平方探測法
  • 鏈地址法

常用哈希函數:課本常介紹除留餘數法,Fasttext用的是FNV-1a,文件校驗常用MD5、SHA-1

大數相加,大數相乘

TODO

判斷完全二叉樹、滿二叉樹、二叉搜索樹BST

判斷二叉樹是否爲完全二叉樹

以層次遍歷的方法, 找到第一個兩個兒子不都存在的節點
if 此節點沒有左子樹且有右子樹:
	不是完全二叉樹
else 看下一個節點:
	如果下一個節點是葉子,則是完全二叉樹,否則不是完全二叉樹

判斷是否爲滿二叉樹

# 返回是否爲滿二叉樹以及該樹的最大深度
def is is_full(p):
	if p is None:
		return True, 0
	else:
		lres, lhei = is_full(p.left)
		rres, rhei = is_full(p.right)
		if lres and rres and lhei==rhei: # 左子樹和右子樹都是滿二叉樹且高度相等
			return True, lhei+1
		else:
			return False, max(lhei, rhei)+1

判斷是否爲BST

中序遍歷的結果若有序則是BST,否則不是

鏈表反轉

頭插法

反轉二叉樹(鏡像二叉樹)

交換樹中所有節點的左右子節點,即得到樹的鏡像。

def invert(root):
	if root is None:
		return root
	tmp = root.left
	root.left = invert(root.right)
	root.right= invert(tmp)
	return root

穩定和非穩定的排序算法有哪些

穩定算法包括:歸併、冒泡、插入、基數。“歸泡插基”

在這裏插入圖片描述

二分查找遞歸和非遞歸的時間和空間複雜度

非遞歸:時間 O(logn)O(\log n),空間 O(1)O(1)

遞歸:時間 O(logn)O(\log n),空間 O(logn)O(\log n)

輸入補全可以用哪個數據結構來做

字典樹,見:如何實現搜索框的關鍵詞提示功能

編輯距離

LeetCode動態規劃題目總結

和等於 k 的最短子數組長度

要求:如果用動態規劃要優化時間,用貪心法需要證明

Leetcode有道類似的:

和等於 k 的最長子數組長度

判斷是否爲對稱二叉樹

def sym(p, q):
    if p is None and q is None:
        return True
    elif p is not None and q is not None:
        # 對稱的條件:p和q的值相同,並且p的左子樹與q的右子樹對稱,p的右子樹與q的左子樹對稱
        return (p.val == q.val) and sym(p.left,q.right) and sym(p.right,q.left)
    return False
    
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        return root is None or sym(root.left,root.right)

旋轉數組:不用額外內存

def reverse(nums,low,high):
    while low < high:
        nums[low], nums[high] = nums[high], nums[low]
        low+=1
        high-=1

class Solution:
    # Do not return anything, modify nums in-place instead.
    def rotate(self, nums: List[int], k: int) -> None:   
        k %= len(nums)
        reverse(nums, 0, len(nums)-1)
        reverse(nums, 0, k - 1)
        reverse(nums, k, len(nums) - 1)

二叉樹的序列化與反序列化

參考: 劍指offer:二叉樹序列化與反序列化

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

# 我把原文中list改爲deque,因爲list的pop(0)複雜度高,可以用deque的popleft代替
from collections import deque
class Solution:
    def Serialize(self, root):
        if not root:
            return '#'
        return str(root.val) +',' + self.Serialize(root.left) +','+ self.Serialize(root.right)
    def Deserialize(self, s):
        nodes = deque(s.split(','))
        return self.deserializeTree(nodes)
    
    def deserializeTree(self, nodes):
        if len(nodes)<=0:
            return None
        val = nodes.popleft()
        root = None
        if val != '#':
            root = TreeNode(int(val))
            root.left = self.deserializeTree(nodes)
            root.right = self.deserializeTree(nodes)
        return root

第k大的數,兩種做法 (quick select, heap)

在從小到大排序後的數組中,第kk大的數是從11開始算的第nk+1n-k+1小的數,下標從 00 開始,因此第kk大的數下標nkn-k.

方法一:quick select

class Solution {
public:
    int partition(vector<int>& arr,int low,int high){
        int r = (rand() % (high-low+1))+ low; // [low, high]
        swap(arr[r], arr[low);
        int pivot = arr[low];      
        while(low<high){
            while(low<high && arr[high]>=pivot)high--;
            arr[low]=arr[high];
            while(low<high && arr[low]<=pivot)low++;
            arr[high]=arr[low];
        }
        arr[low]=pivot;
        return low;
    }
    int findKthLargest(vector<int>& nums, int k) {
        int mid=-1, low=0, high=nums.size()-1;
        while(true){
            mid = partition(nums,low,high);
            if(mid==nums.size()-k){
                return nums[mid];
            }else if(mid<nums.size()-k){
                low=mid+1;
            }else{
                high=mid-1;
            }
        }
    }
};

使用優先隊列,用法見:c++優先隊列(priority_queue)用法詳解

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
    	// 默認是大頂堆,使用greater<T> 是小頂堆
        priority_queue<int,vector<int>,greater<int>> q; 
        for(auto it:nums){
            q.push(it);
            // pop掉最小的,剩下k個最大的,最後的top()就是k個最大中的最小值
            if(q.size()>k) q.pop();
        }
        return q.top();
    }
};

python解法:

from queue import PriorityQueue

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        pq=PriorityQueue()
        for x in nums:
            pq.put(x)
            if pq.qsize()>k:
                pq.get()
        return pq.get()

判斷鏈表是否有環以及環的位置,證明

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head or not head.next: # 空鏈表或只有一個節點時沒有環
            return False
        slow,fast=head,head
        while fast and fast.next:
            slow=slow.next
            fast=fast.next.next
            if slow==fast:
                break
        return slow==fast

證明尋找環的起點的算法:

http://www.bnee.net/article/20526.html

在這裏插入圖片描述

1、慢指針slow走到了環入口時,設共走了k步。此時快指針fast越過了環入口的步數爲delta。因爲快指針可能繞着環走了很多圈,所以有k = delta + n * R,其中R爲環的大小,n爲快指針繞環走的圈數。

2、證明必然會相遇。慢指針進入環中後,因爲快指針每次都比慢指針快一步,所以,快慢指針最後一定會相遇。

在這裏插入圖片描述
3、計算快慢指針相遇位置。因爲慢指針在剛進入環時距離快指針delta步,所以快指針還需要比慢指針多走R - delta步才能與慢指針相遇。又因爲快指針每次走兩步,所以快指針還需要走2(R - delta)步。那麼,相對環的起點而言,相遇位置爲2(R - delta) + delta = 2R - delta,即,距離環入口delta處,與慢指針剛進入環時快指針所在位置對稱。

4、快指針重新從頭結點開始走,速度爲一次一步,與慢指針相同。可知,快指針走到環入口時,所需步數爲k。由於 k = delta + n * R,所以慢指針也是剛好走到環入口。

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return None
        slow,fast=head,head
        while fast and fast.next:
            slow=slow.next
            fast=fast.next.next
            if slow==fast:
                break
        if slow==fast:
            fast=head
            while slow!=fast:
                slow=slow.next
                fast=fast.next
            return slow
        return None

青蛙跳臺階+有一次後退機會

面經看到,但是搜索不到這道題的解法,我目前的個人思路是這樣的:

先當做沒有後退機會來算,然後考慮一次後退機會加在不同位置帶來的新的跳法總共多少種。

設有nn級臺階,分別標號[1,2,...,n][1,2,...,n],我們一開始處於位臺階 00,最後要到達位置nn.

當沒有後退機會時,設從臺階 00 到達臺階 ii 共有的跳法種數是 dp[i]dp[i],則 dp[0]=0,dp[1]=1,dp[i]=dp[i1]+dp[i2],i2dp[0]=0,dp[1]=1,dp[i]=dp[i-1]+dp[i-2],其中i≥2

然後考慮一次後退機會加在不同位置帶來的新的跳法總共多少種

如果後退之後到達的是臺階 ii,那麼產生新的跳法數是 “從臺階 ii 到臺階 nn 的跳法種數”,這應該等價於 “從臺階 00 到臺階 nin-i 的跳法種數”,也就是 dp[ni]dp[n-i].

由上可知,考慮上後退位置的不同,

=退+退=dp[n]+(dp[n]+dp[n1]++dp[1])n退n=dp[n]+i=1ndp[i]\begin{aligned} 總的跳法總數 &= 沒有後退機會的種數+不同位置的一次後退帶來的新增跳法種數\\ &=dp[n]+(dp[n]+dp[n-1]+\cdots +dp[1]) (不能超出第n級再後退到第 n級)\\ &=dp[n]+\sum_{i=1}^n dp[i] \end{aligned}

也就是按照沒有後退機會求出前面所定義的 dpdp 數組後,對數組求和再加上 dp[n]dp[n] 即爲所求,用python就是return dp[n] + sum(dp)

最長公共子串、最長公共子序列

LeetCode動態規劃題目總結(持續更新中)

二叉樹路徑最大和

https://leetcode.com/problems/binary-tree-maximum-path-sum/

蓄水池採樣問題

https://www.jianshu.com/p/7a9ea6ece2af

https://blog.csdn.net/anshuai_aw1/article/details/88750673

給定一個數據流,數據流長度N很大,且N直到處理完所有數據之前都不可知,請問如何在只遍歷一遍數據(O(N))的情況下,能夠隨機選取出k個不重複的數據。

這個場景強調了3件事:

  • 數據流長度N很大且不可知,所以不能一次性存入內存。
  • 時間複雜度爲O(N)。
  • 隨機選取k個數,每個數被選中的概率爲k/N。

思路:

1、前 k 個數據直接放入蓄水池。
2、蓄水池裝滿了 k 個數據之後,當接收到第 i 個數據時,在 [0, i] 範圍內取隨機數 r,若 r 落在[0, k-1]範圍內,則用接收到的第i個數據替換蓄水池中的第 r 個數據。
3、重複步驟2。

核心代碼如下:

int[] reservoir = new int[k];

// init
for (int i = 0; i < reservoir.length; i++){
    reservoir[i] = dataStream[i];
}

for (int i = k; i < dataStream.length; i++){
    // 隨機獲得一個[0, i]內的隨機整數
    int r = rand.nextInt(i + 1);
    // 如果隨機整數落在[0, m-1]範圍內,則替換蓄水池中的元素
    if (r < k){
        reservoir[r] = dataStream[i];
    }
}

:這裏使用已知長度的數組dataStream來表示未知長度的數據流,並假設數據流長度大於蓄水池容量m。

證明:

先求第 ii 個數 (ik)(i≤k) 在第 NN 步之後被保留在蓄水池的概率 P1P_1。在第 k+1k+1 步時,它被保留的概率是 11k+1=kk+11-\frac{1}{k+1}=\frac k{k+1},在第 k+2k+2 步時,被保留的概率是 11k+2=k+1k+21-\frac{1}{k+2}=\frac {k+1}{k+2},以此類推,可知:

P1=k=kk+1×k+1k+2××n2n1×n1n=kn\begin{aligned} P_1 &= 第 k 步後的每一步都被保留在蓄水池的概率的乘積\\ &=\frac k{k+1} \times \frac {k+1}{k+2} \times \cdots\times \frac {n-2}{n-1} \times \frac {n-1}{n}\\ &=\frac{k}{n} \end{aligned}

再求第 jj 個數 (j>k)(j>k) 在第 NN 步之後被保留在蓄水池的概率 P2P_2。最後要保留必須在遇到它時將該數用於替換蓄水池中的數據,而且之後的每一步他都沒有被替換掉。在第 jj 步時,該數被保留下來的概率是 kj\frac k{j},在第 j+1j+1 步時,被保留在蓄水池的概率是 11j+1=jj+11-\frac{1}{j+1}=\frac{j}{j+1},以此類推,可知:

P2=j=kj×jj+1×n2n1×n1n=kn\begin{aligned} P_2 &= 第 j 步被保留的概率乘以後面每一步都被保留的概率\\ &=\frac k{j} \times \frac{j}{j+1}\cdots\times \frac {n-2}{n-1} \times \frac {n-1}{n}\\ &=\frac{k}{n} \end{aligned}

綜上可知,每個數在第 NN 步之後被保留的概率都是 kn\frac{k}{n}

2 sum,3 sum

【算法】2SUM/3SUM/4SUM問題

2sum的思路和python代碼:

先掃描一次數組建立一個hash_map,存放key爲某個數,value爲其在nums中出現的下標(可能出現多次,所以value是list)

然後再掃一遍這個數組,對於每個num[i],判斷target-nums[i]是否存在,如果存在則說明有,不存在繼續找。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        num2ids={}  # 某個數在nums中出現的下標(可能出現多次,所以value是list)
        for i in range(len(nums)):
            if nums[i] in num2ids:
                num2ids[nums[i]].append(i)
            else:
                num2ids[nums[i]]=[i]
        for i in range(len(nums)):
            a=nums[i]  # 固定a,則我們要看 target-a 是否在nums中出現
            b=target-a
            if b==a:  # 要注意的是如果 target-a 就等於 a自己,則a需要在nums出現兩次
                if a in num2ids and len(num2ids[a])>1:
                    return num2ids[a][:2]  # 返回a出現的前兩次下標
            else:
                if b in num2ids and len(num2ids[b])>0:
                    return [i]+[num2ids[b][0]]  # 返回a出現的下標和b出現的下標
            

3sum,複雜度O(n2)O(n^2),參考:這個帖子

class Solution(object):
    def threeSum(self, nums):
        res = []
        nums.sort()
        length = len(nums)
        for i in range(length - 2):  # 從前n-2個數字中固定a,來找b和c

            if i > 0 and nums[i] == nums[i - 1]:
                continue  # 已經選過這個數字作爲a

            l, r = i + 1, length - 1  # 在a的右邊範圍的左右端點開始夾逼找b和c
            while l < r:
                total = nums[i] + nums[l] + nums[r]

                if total < 0:  # 如果求和比0小,說明需要更大的b
                    l += 1
                elif total > 0:  # 如果求和比0小,說明需要更小的c
                    r -= 1
                else:  # 找到了一對組合
                    res.append([nums[i], nums[l], nums[r]])
                    
                    # 在a固定的情況下,不可能再有一對數與剛找到的有相同的b,所以跳過所有相同的b
                    while l < r and nums[l] == nums[l + 1]:
                        l += 1
                    l += 1  # 前面while得到的是最右邊的b的下標,下次需要換個b,所以要再右移一步

                    # 在a固定的情況下,不可能再有一對數與剛找到的有相同的c,所以跳過所有相同的c
                    while l < r and nums[r] == nums[r - 1]:
                        r -= 1
                    r -= 1  # 前面while得到的是最左邊的c的下標,下次需要換個c,所以要再左移一步

        return res

一個排序數組能夠構成多少個二叉搜索樹

https://leetcode.com/problems/unique-binary-search-trees/

LeetCode動態規劃題目總結(持續更新中)

分解質因數

def get_num_factors(num):
    res = []
    tmp = 2
    if num <= 3:
        return [num]
    while num >= tmp:
        if num % tmp == 0:
            res.append(tmp)
            num = num // tmp  # 更新
        else:
            tmp = tmp + 1  # 同時更新除數值,不必每次都從頭開始
    return res

接雨水

題目鏈接:LC42. Trapping Rain Water

在這裏插入圖片描述

# 參考:https://zhuanlan.zhihu.com/p/79811305
class Solution:
    def trap(self, height: List[int]) -> int:
        n=len(height)
        left_max=[0]*n  # 第i號柱子左邊最高的柱子高度
        right_max=[0]*n  # 第i號柱子右邊最高的柱子高度
        
        # 第0號柱子左邊最高爲0,從第1號柱子開始求
        for i in range(1,n):  
            # 比較第i-1號柱子的高度與第i-1號柱子左邊最高柱子的高度
            left_max[i]=max(height[i-1],left_max[i-1])
            
        # 第n-1號柱子右邊最高爲0,從第n-2號柱子開始求
        for i in range(n-2,-1,-1):  
            right_max[i]=max(height[i+1],right_max[i+1])
            
        volume=0
        for i in range(n):
            # 對於每一個柱子,求出左右兩邊最高柱子的短板
            # 它限制了當前柱子上方能容納多少雨水
            low=min(left_max[i],right_max[i])
            if low>height[i]:
                volume+=low-height[i]
        return volume
            

旋轉數組的最小數字

在這裏插入圖片描述
題目鏈接:旋轉數組的最小數字

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, arr):
        # write code here
        if len(arr)==0:
            return 0
        low , high = 0, len(arr) - 1
        while low<high:
            mid=(high-low)//2+low
            if arr[mid]<arr[high]:
                # 說明mid到high是遞增的,最小元素肯定不在mid的右邊,必然在mid或mid的左邊,例如[4,5,1,2,3]或[1,2,3,4,5]
                high=mid
            elif arr[mid]>arr[high]:
                # 此時mid肯定位於旋轉數組的左半部分,例如[3,4,5,1,2],此時最小元素肯定在mid右邊
                low=mid+1
            else:
                # 當arr[mid]==arr[high]時,無法確定最小元素位於mid左邊還是右邊,只好一個一個找
                # 例如:[1,0,1,1,1]和[1,1,1,0,1]
                # 由於arr[high]==arr[mid],因此至少可以將high縮小一位(最多到達mid的位置)
                high-=1
        return arr[low]
        

最小的K個數

使用大頂堆求解,注意python中的PriorityQueue默認是小頂堆,若要使用大頂堆,可以將 put(x) 改爲 put(-x),並將 get() 改爲 -get()

# -*- coding:utf-8 -*-
try:
    from queue import PriorityQueue  # python3,若確定是py3可直接寫這句
except:
    from Queue import PriorityQueue  # python2,若確定是py2可直接寫這句


class MaxHeap:
    def __init__(self):
        self.pq = PriorityQueue()

    def put(self, x):
        self.pq.put(-x)

    def get(self):
        return -self.pq.get()

    def empty(self):
        return self.pq.empty()

    def qsize(self):
        return self.pq.qsize()


class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        if len(tinput)*k == 0 or k > len(tinput):
            return []
        pq = MaxHeap()
        for x in tinput:
            if pq.qsize() < k:
                pq.put(x)
            else:
                e = pq.get()
                x = min(x, e)
                pq.put(x)
        res = []
        while not pq.empty():
            res.append(pq.get())
        return res[::-1]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章