基本原理就不做介紹了, 很基礎的數據結構課程知識.私下回顧即可,主要學習代碼.
1.雙指針
題目:
給定一個已按照升序排列的有序數組,找到兩個數使得它們相加之和等於目標數。
函數應該返回這兩個下標值 index1 和 index2,其中 index1 必須小於 index2。
說明:
返回的下標值(index1 和 index2)不是從零開始的。
你可以假設每個輸入只對應唯一的答案,而且你不可以重復使用相同的元素。
示例:
輸入: numbers = [2, 7, 11, 15], target = 9
輸出: [1,2]
解釋: 2 與 7 之和等於目標數 9 。因此 index1 = 1, index2 = 2 。
分析:
利用排序數組的性質,采用雙指針來在時間O(n)和空間O(1)解決。
1.特殊判定, 若數組為空,返回[]
2.定義左指針L=0指向最小元素,定義右指針R=n-1指向最大元素
3.循環條件: L<R
若number[L] + number[R] == target, 則返回[L+1, R+1]
若number[L] + number[R] > target, 說明右邊的值太大,調小一點,即R=R-1
若number[L] + number[R] < target, 說明左邊的值太小,調大一點,即L= L+1
4.執行到這一步,若不返回,說明無解.返回[-1, -1]
復雜度分析:
時間復雜度:O(n)
空間復雜度:O(l)
class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: if(not numbers): return [] # res=[] n=len(numbers) l=0 r=n-1 while(l<r): if(numbers[l]+numbers[r]==target): return [l+1,r+1] elif(numbers[l]+numbers[r]>target): r=r-1 else: l=l+1 return [-1,-1]
2.快速選擇,堆排序,歸並排序
題目:
215題: 數組中的第K個最大元素
在未排序的數組中找到第K個最大元素.請注意,你需要找的是數組排序后的第K個最大的元素,而不是第K個不同的元素.
示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5
示例 2:
輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4
說明:
你可以假設 k 總是有效的,且 1 ≤ k ≤ 數組的長度。
分析:
方法0:排序
最朴素的方法是先對數組進行排序,再返回倒數第K個元素,就像python中的sorted(nums)[-k]
算法的時間復雜度為O(NlogN),空間復雜度為O(1).這個時間復雜度並不令人滿意,試着用額外空間來優化時間復雜度.
方法1:堆
創建一個小頂堆,將所有數組中的元素加入堆中,並保持堆的大小<=k.這樣堆中就保留了前k個最大元素.此時,堆頂的元素就是正確答案.
向大小為k的堆中添加元素的時間復雜度是O(logk),重復該操作N次,總時間復雜度為O(Nlogk).
python的heapq庫中有一個nlargest方法,具有同樣的時間復雜度,能將代碼簡化到一行.
本方法優化了時間復雜度,但需要O(k)的空間復雜度.
class Solution: def findKthLargest(self, nums, k): """ :type nums: List[int] :type k: int :rtype: int """ return heapq.nlargest(k, nums)[-1]
復雜度分析:
時間復雜度 :O(Nlogk)。
空間復雜度 : O(k),用於存儲堆元素。
方法2:快速選擇
這種方法應該叫快速選擇(Quick Select),跟快排的思想是非常接近的:選取一個基准元素pivot,將數組切分(partition)為兩個子數組,比pivot大的扔左子數組,比pivot小的扔右子數組,然后遞推地切分子數組。Quick Select不同於Quick Sort的是其沒有對每個子數組做切分,而是對目標子數組做切分。
快速選擇算法 的平均時間復雜度為O(N)。就像快速排序那樣.
注意到第k個最大元素也是第n-k+1個最小元素,因此可以用第k小算法來解決本問題.
首先,我們選擇一個樞軸,並在線性時間內定義其在排序數組中的位置。這可以通過 划分算法 的幫助來完成。
為了實現划分,沿着數組移動,將每個元素與樞軸進行比較,並將小於樞軸的所有元素移動到樞軸的左側。
這樣,在輸出的數組中,樞軸達到其合適位置。所有小於樞軸的元素都在其左側,所有大於或等於的元素都在其右側。
這樣,數組就被分成了兩部分。如果是快速排序算法,會在這里遞歸地對兩部分進行快速排序,時間復雜度為O(NlogN)。
由於知道要找的第 N - k
+ 1小的元素在哪部分中,我們不需要對兩部分都做處理,這樣就將平均時間復雜度下降到 O(N)。
最終的算法十分直截了當 :
隨機選擇一個樞軸。
使用划分算法將樞軸放在數組中的合適位置 pos。將小於樞軸的元素移到左邊,大於等於樞軸的元素移到右邊。
比較 pos 和 N - k +1以決定在哪邊繼續遞歸處理。
注意,本算法也適用於有重復的數組
""" Quick Select的目標是找出第k大元素,所以: 若切分后的左子數組的長度 > k,則第k大元素必出現在左子數組中; 若切分后的左子數組的長度 = k - 1,則第k大元素為pivot; 若上述兩個條件均不滿足,則第k大元素必出現在右子數組中。 基於上述,則可以很容易的得出Quick Select的實現代碼: """
class Solution: def findKthLargest(self, nums, k): """ :type nums: List[int] :type k: int :rtype: int """ def partition(left, right, pivot_index): pivot = nums[pivot_index] # 1. move pivot to end nums[pivot_index], nums[right] = nums[right], nums[pivot_index] # 2. move all smaller elements to the left store_index = left for i in range(left, right): if nums[i] < pivot: nums[store_index], nums[i] = nums[i], nums[store_index] store_index += 1 # 3. move pivot to its final place nums[right], nums[store_index] = nums[store_index], nums[right] return store_index def select(left, right, k_smallest): """ Returns the k-th smallest element of list within left..right """ if left == right: # If the list contains only one element, return nums[left] # return that element # select a random pivot_index between pivot_index = random.randint(left, right) # find the pivot position in a sorted list pivot_index = partition(left, right, pivot_index) # the pivot is in its final sorted position if k_smallest == pivot_index: return nums[k_smallest] # go left elif k_smallest < pivot_index: return select(left, pivot_index - 1, k_smallest) # go right else: return select(pivot_index + 1, right, k_smallest) # kth largest is (n - k)th smallest return select(0, len(nums) - 1, len(nums) - k)
復雜度分析
- 時間復雜度 : 平均情況 O(N),最壞情況 O(N2)。
- 空間復雜度 : O(1)。
3.桶排序
題目:
347題: 前K個高頻元素
給定一個非空的整數數組,返回其中出現頻率前 k 高的元素。
示例 1:
輸入: nums = [1,1,1,2,2,3], k = 2
輸出: [1,2]
示例 2:
輸入: nums = [1], k = 1
輸出: [1]
說明:
你可以假設給定的 k 總是合理的,且 1 ≤ k ≤ 數組中不相同的元素的個數。
你的算法的時間復雜度必須優於 O(n log n) , n 是數組的大小。
方法1:使用Counter
思路:
統計每個數出現的頻率,輸出最大的幾個,這完全迎合了Python中的Counter類,調用其的幾個方法即可。
什么是Counter?
Counter 是一個在collections包里的類,正如其名,是一個用於計數的工具。
我們可以用Counter(nums)這樣的構造函數構造一個Counter類,其中nums是一個列表。
構造好的Counter實例可以看作一個字典,鍵是nums的每一項,值是它的出現次數。
如果上面的敘述讓你感到很混亂的話,我不妨舉個例子。
如果一個列表a = [1,1,3,4,3],你想要統計每項的出現次數,那么你使用b = Counter(a),那么這時候b就像一個這樣的字典{1:2,3:2,4:1},表示數字1出現了2次,數字3出現了2次,數字4出現了1次。
還是很好理解的吧?
可是題目里要我們輸出的是最多的K項
這時候可以應用Counter的一個函數,most_common(k)
這個函數就是返回最多出現的K項
但是返回的形式是一個元祖列表,類似[(1,2),(3,2),(4,1)]的形式
我們只需要鍵也就是第一項,所以要使用列表生成式處理一下即可。
from collections import Counter class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: return [i[0] for i in Counter(nums).most_common(k)]
方法2:桶排序
桶排序科普:https://www.cnblogs.com/bqwzx/p/11029264.html
class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: map = {} for i in nums: map[i] = map.get(i,0) + 1 #生成字典映射 max_time = max(map.values()) TongList = [[] for i in range(max_time+1)] #根據最大次數生成桶 for key, value in map.items(): TongList[value].append(key) #將索引value放入key對應的字典索引 res = [] for i in range(max_time, 0, -1): #按桶索引排序 if TongList[i]: res.extend(TongList[i]) if len(res) >= k: return res[:k]
方法3:
第一步:hash統計次數(在Python中要巧用字典)
第二步:字典鍵值對壓入大頂堆(注意如何將heapq小頂堆作為大頂堆使用)
第三步:大頂堆吐出k個值加入返回list
import heapq class Solution: def topKFrequent(self, nums, k): heap_max = [] //初始化一個大頂堆 dic_fre = {} //初始化一個字典 ans = [] for i in nums: //先將nums中的數字依次讀入字典 if i in dic_fre: dic_fre[i]+=1 //如果字典中存在同樣的i值,說明是第2次出現,將dic_fre[i]位置上的值累加計數 else: dic_fre[i] = 1 //如果沒有, 則將dic_fre[i]位置上的值置1 for i in dic_fre: heapq.heappush(heap_max,(-dic_fre[i],i)) //將字典中的key讀入大頂堆(push) for j in range(k): p = heapq.heappop(heap_max) // 將大頂堆中出現頻率前k的取出(pop) ans.append(p[1]) //保存為list形式輸出 return ans
方法4:
定義一個字典來記錄列表nums中每個元素出現的次數,計入字典
然后將字典按值逆向排序,輸出前k個鍵.
class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: dic = {} for i in nums: if i in dic: dic[i] += 1 else: dic[i] = 0 res = sorted(dic.items(),key=lambda x:x[1], reverse=True) return [res[i][0] for i in range(k)]
4.滑動窗口
題目:
leetcode 209: 長度最小的子數組
給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的子數組。如果不存在符合條件的子數組,返回 0。
示例:
輸入: [2,3,1,2,4,3], s = 7
輸出: 2
解釋: 子數組 [4,3] 是該條件下的長度最小的子數組。
方法1:
首先想到的方案是暴力破解,通過遍歷所有的子數組找到滿足條件的最小子數組。
class Solution: def minSubArrayLen(self, s, nums): """ :type s: int :type nums: List[int] :rtype: int """ minLen = len(nums) + 1 for i, _ in enumerate(nums): sum_all = 0 for j, tmp in enumerate(nums[i:]): sum_all += tmp if sum_all >= s: minLen = min(minLen, j + 1) if minLen == len(nums) + 1: return 0 return minLen
這種方法屬於O(n^2)
解法,我們通過分析發現我們上述算法中有大量重疊運算。我們有沒有更快的做法?
這個時候我們就會想到之前Leetcode 167:兩數之和 II - 輸入有序數組提到的對撞指針
。我們可以建立兩個指針,通過累加兩個指針的區間內的值和s
比較,就可以在O(n)
級別的時間內得到結果。
2 3 1 2 4 3 l <- -> r
由於兩個指針移動的過程中,指針之間的距離就像一個窗口一樣,我們通過控制窗口的大小,得到我們想要的結果。我們稱這種問題是一個滑動窗口問題。
class Solution: def minSubArrayLen(self, s, nums): """ :type s: int :type nums: List[int] :rtype: int """ l = 0 r = 0 sum_all = 0 nums_len = len(nums) minLength = nums_len + 1 while l < nums_len: if r < nums_len and sum_all < s://如果右指針未出界(<nums_len)且窗口內元素之和尚且小於s sum_all += nums[r] //將目前的sum_all加上目前的nums[r]值 r += 1 //且右指針右移一位 else: sum_all -= nums[l] //否則, 左指針需要向右滑動一位, 則原來窗口中的nums[l]被移出窗外,需減去. l += 1 //左指針右移一位 if sum_all >= s: //窗口內數字之和滿足條件,在minLength和窗口長度中(r-l)中選擇最小的作為最終的minLength minLength = min(minLength, r - l) if minLength == nums_len + 1: return 0 return minLength
leetcode 438: 找出所有變位詞(即元素位置/順序發生了變化,但是整體元素一致)
Given a string s and a non-empty string p, find all the start indices of p’s anagrams in s.
Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.
The order of output does not matter.
Example 1:
Input:
s: “cbaebabacd” p: “abc”
Output:
[0, 6]
Explanation:
The substring with start index = 0 is “cba”, which is an anagram of “abc”.
The substring with start index = 6 is “bac”, which is an anagram of “abc”.
Example 2:
Input:
s: “abab” p: “ab”
Output:
[0, 1, 2]
Explanation:
The substring with start index = 0 is “ab”, which is an anagram of “ab”.
The substring with start index = 1 is “ba”, which is an anagram of “ab”.
The substring with start index = 2 is “ab”, which is an anagram of “ab”.
本題是滑動窗口方法的一個應用,另外,本題處理hash的方式也是被經常使用的,需要注意。
哈希方式
分析題目,要找的是待查詢序列的一個子序列,這個子序列可以是給定目標序列的任意順序。我們首先要考慮如何滿足所謂的任意順序。這里很容易想到采取一種哈希策略,使包含目標序列的字符擁有相同的哈希值,從而任何順序的字符序列都可以被快速識別出來。這樣的想法是完全正確的,相比去產生所有可能的序列,這種方法無疑擁有更低的時間復雜度。這種方法還是比較好想到,只需要一個字符數組,保存目標序列各個字符出現的次數即可。這樣,只要滿足對應數組結構的子序列,就是一個符合要求的子序列。
🔺滑動窗口(sliding window)
字符串匹配問題一個常見的解法就是滑動窗口了,我們利用上述得到的哈希數組,來移動滑動窗口,若窗口內的字符串滿足該數組,我們就記錄窗口左部的下標作為返回值之一。本題中,滑動窗口的大小是判斷是否移動左側下標的條件,只有滑動窗口的長度達到目標序列后,左側下標才會前移一位。另外,本題中我們使用變量count來表示窗口內剩余的目標字符個數,該個數為0表示窗口內的字符是要求的字符,這個值會在窗口滑動過程中得到更新。
class Solution: def findAnagrams(self, s, p): """ :type s: str :type p: str :rtype: List[int] """ from collections import Counter s_len, p_len = len(s), len(p) //源string和子string的長度,本例s_len=10, p_len=3 count = p_len //設置計數器初始大小=子string的長度 pChar = Counter(p) //pChar: Counter({'a':1, 'b':1, 'c':1}) result = [] //初始化一個空list,記錄結果子數組的下標起始位置的index,並且作為最后的結果返回輸出. for i in range(s_len)://遍歷源數組,i:0-10 if pChar[s[i]] >= 1: //pChar[s[i]]: pChar[s[0]]=pChar[c]=1 count -= 1 //count減1. count為0表示沒有需要匹配的字符了,此時窗口內的字符都是要求的字符. pChar[s[i]] -= 1 //pChar[*]字符數量減1 if i >= p_len://當遍歷到的索引值大於子數組長度 if pChar[s[i - p_len]] >= 0: //說明我們去掉了一個屬於p的字符,所以需要匹配的字符數count需要增加一個,count自增1 count += 1 pChar[s[i - p_len]] += 1 //pChar[*]字符數量加1 if count == 0://count為0,表示沒有需要匹配的字符了,即所有字符均被匹配到了。將有效數組起始位置index存入result中。 result.append(i - p_len + 1) //記錄有效數組起始位置index,index計算方式:i - p_len + 1 return result if __name__ == "__main__": s = "cbaebabacd" p = "abc" print(Solution().findAnagrams(s, p))
*如果上述程序運行不了,把注釋去掉即可。
以免理解仍有難度,再貼上一個解釋:
首先統計字符串p的字符個數,然后用兩個變量left和right表示滑動窗口的左右邊界,用變量count表示字符串p中需要匹配的字符個數,然后開始循環,如果右邊界的字符已經在哈希表中了,說明該字符在p中有出現,則count自減1,然后哈希表中該字符個數自減1,右邊界自加1,如果此時count減為0了,說明p中的字符都匹配上了,那么將此時左邊界加入結果result中。如果此時right和left的差為p的長度,說明此時應該去掉最左邊的一個字符,我們看如果該字符在哈希表中的個數大於等於0,說明該字符是p中的字符,為啥呢,因為上面我們有讓每個字符自減1,如果不是p中的字符,那么在哈希表中個數應該為0,自減1后就為-1,所以這樣就知道該字符是否屬於p,如果我們去掉了屬於p的一個字符,count自增1.
leetcode 76:最小覆蓋字串
給你一個字符串 S、一個字符串 T,請在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
輸入: S = "ADOBECODEBANC", T = "ABC"
輸出: "BANC"
說明:
如果 S 中不存這樣的子串,則返回空字符串 ""。
如果 S 中存在這樣的子串,我們保證它是唯一的答案。
分析:
題目不難理解,就是說要在 S(source) 中找到包含 T(target) 中全部字母的一個子串,順序無所謂,但這個子串一定是所有可能子串中最短的。
滑動窗口算法的思路是這樣:
1、我們在字符串 S 中使用雙指針中的左右指針技巧,初始化 left = right = 0,把索引閉區間 [left, right] 稱為一個「窗口」。
2、我們先不斷地增加 right 指針擴大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
3、此時,我們停止增加 right,轉而不斷增加 left 指針縮小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同時,每次增加 left,我們都要更新一輪結果。
4、重復第 2 和第 3 步,直到 right 到達字符串 S 的盡頭。
這個思路其實也不難,第 2 步相當於在尋找一個「可行解」,然后第 3 步在優化這個「可行解」,最終找到最優解。左右指針輪流前進,窗口大小增增減減,窗口不斷向右滑動。
下面畫圖理解一下,needs 和 window 相當於計數器,分別記錄 T 中字符出現次數和窗口中的相應字符的出現次數。
初始狀態:
增加 right,直到窗口 [left, right] 包含了 T 中所有字符:
現在開始增加 left,縮小窗口 [left, right]。
直到窗口中的字符串不再符合要求,left 不再繼續移動。
之后重復上述過程,先移動 right,再移動 left…… 直到 right 指針到達字符串 S 的末端,算法結束。
如果你能夠理解上述過程,恭喜,你已經完全掌握了滑動窗口算法思想。至於如何具體到問題,如何得出此題的答案,都是編程問題,等會提供一套模板,理解一下就會了。
string s, t; // 在 s 中尋找 t 的「最小覆蓋子串」 int left = 0, right = 0; string res = s; while(right < s.size()) { window.add(s[right]); right++; // 如果符合要求,移動 left 縮小窗口 while (window 符合要求) { // 如果這個窗口的子串更短,則更新 res res = minLen(res, window); window.remove(s[left]); left++; } } return res;
如果上述代碼你也能夠理解,那么你離解題更近了一步。現在就剩下一個比較棘手的問題:如何判斷 window 即子串 s[left...right] 是否符合要求,是否包含 t 的所有字符呢?
可以用兩個哈希表當作計數器解決。用一個哈希表 needs 記錄字符串 t 中包含的字符及出現次數,用另一個哈希表 window 記錄當前「窗口」中包含的字符及出現的次數,如果 window 包含所有 needs 中的鍵,且這些鍵對應的值都大於等於 needs 中的值,那么就可以知道當前「窗口」符合要求了,可以開始移動 left 指針了。
現在將上面的框架繼續細化:
string s, t; // 在 s 中尋找 t 的「最小覆蓋子串」 int left = 0, right = 0; string res = s; // 相當於兩個計數器 unordered_map<char, int> window; unordered_map<char, int> needs; for (char c : t) needs[c]++; // 記錄 window 中已經有多少字符符合要求了 int match = 0; while (right < s.size()) { char c1 = s[right]; if (needs.count(c1)) { window[c1]++; // 加入 window if (window[c1] == needs[c1]) // 字符 c1 的出現次數符合要求了 match++; } right++; // window 中的字符串已符合 needs 的要求了 while (match == needs.size()) { // 更新結果 res res = minLen(res, window); char c2 = s[left]; if (needs.count(c2)) { window[c2]--; // 移出 window if (window[c2] < needs[c2]) // 字符 c2 出現次數不再符合要求 match--; } left++; } } return res;
上述代碼已經具備完整的邏輯了,只有一處偽碼,即更新 res 的地方,不過這個問題太好解決了,直接看解法吧!
string minWindow(string s, string t) { // 記錄最短子串的開始位置和長度 int start = 0, minLen = INT_MAX; int left = 0, right = 0; unordered_map<char, int> window; unordered_map<char, int> needs; for (char c : t) needs[c]++; int match = 0; while (right < s.size()) { char c1 = s[right]; if (needs.count(c1)) { window[c1]++; if (window[c1] == needs[c1]) match++; } right++; while (match == needs.size()) { if (right - left < minLen) { // 更新最小子串的位置和長度 start = left; minLen = right - left; } char c2 = s[left]; if (needs.count(c2)) { window[c2]--; if (window[c2] < needs[c2]) match--; } left++; } } return minLen == INT_MAX ? "" : s.substr(start, minLen); }
如果直接甩給你這么一大段代碼,我想你的心態是爆炸的,但是通過之前的步步跟進,你是否能夠理解這個算法的內在邏輯呢?你是否能清晰看出該算法的結構呢?
這個算法的時間復雜度是 O(M+N),M 和 N 分別是字符串 S 和 T 的長度。因為我們先用 for 循環遍歷了字符串 T 來初始化 needs,時間 O(N),之后的兩個while 循環最多執行 2M 次,時間O(M)。
讀者也許認為嵌套的 while 循環復雜度應該是平方級,但是你這樣想,while 執行的次數就是雙指針 left 和 right 走的總路程,最多是 2M 嘛。
*這一段不是python編程,但是算法思想很通透,值得借鑒學習。附上一個python滑窗代碼:
class Solution: def minWindow(self, s: 'str', t: 'str') -> 'str': from collections import defaultdict lookup = defaultdict(int) for c in t: lookup[c] += 1 start = 0 end = 0 min_len = float("inf") counter = len(t) res = "" while end < len(s): if lookup[s[end]] > 0: counter -= 1 lookup[s[end]] -= 1 end += 1 while counter == 0: if min_len > end - start: min_len = end - start res = s[start:end] if lookup[s[start]] == 0: counter += 1 lookup[s[start]] += 1 start += 1 return res
附加題:
1、數組中重復的數字
在一個長度為n的數組里的所有數字都在0到n-1的范圍內。 數組中某些數字是重復的,但不知道有幾個數字是重復的。也不知道每個數字重復幾次。請找出數組中任意一個重復的數字。 例如,如果輸入長度為7的數組{2,3,1,0,2,5,3},那么對應的輸出是第一個重復的數字2。
方法1:利用字典(哈希)
# -*- coding:utf-8 -*- class Solution: # 這里要特別注意~找到任意重復的一個值並賦值到duplication[0] # 函數返回True/False def duplicate(self, numbers,duplication): dic = {} for num in numbers: if not num in dic: dic[num] = 1 else: dic[num] += 1 for num in numbers: if dic[num] != 1: duplication[0] = num return True return False
方法2:高贊回答里的“高級方法”
# -*- coding:utf-8 -*- class Solution: # 這里要特別注意~找到任意重復的一個值並賦值到duplication[0] # 函數返回True/False def duplicate(self, numbers, duplication): index = 0 while index < len(numbers): if numbers[index] == index: index += 1 elif numbers[index] == numbers[numbers[index]]: duplication[0] = numbers[index] return True else: index_2 = numbers[index] numbers[index],numbers[index_2] = numbers[index_2],numbers[index] return False
2.構建乘積數組
給定一個數組A[0,1,...,n-1],請構建一個數組B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:規定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
思路一:
兩重循環,在遍歷數組A的時候,A[i]賦值為1,計算B[i]。時間復雜度為O(N^2)
-*- coding:utf-8 -*- class Solution: def multiply(self, A): # write code here length = len(A) B = [] if(length == 0): return B for i in range(length): temp = A[i] A[i] = 1 Bi = 1 for j in A: Bi *= j B.append(Bi) A[i] = temp return B
or
# -*- coding:utf-8 -*- class Solution: def multiply(self, A): # write code here B = [] length = len(A) for i in range(length): count = 1 for j in range(length): if j == i: continue count *= A[j] B.append(count) return B
思路二:
時間復雜度為O(N)
可以把B[i]=A[0]A[1]....A[i-1]A[i+1]....A[n-1]。看成A[0]A[1].....A[i-1]和A[i+1].....A[n-2]A[n-1]兩部分的乘積。
即通過A[i]項將B[i]分為兩部分的乘積。效果相當於是個對角矩陣。
第一個for循環用來計算上圖1范圍的數,第二個for循環用來計算上圖2范圍的數。
# -*- coding:utf-8 -*- class Solution: def multiply(self, A): # write code here length = len(A) if(length == 0): return [] import copy B = copy.copy(A) B[0] = 1 # 計算下三角連乘 for i in range(1,length): B[i] = B[i-1] * A[i-1] # 計算上三角連乘 temp = 1 for j in range(length-2,-1,-1): temp *= A[j+1] B[j] *= temp return B
or
# -*- coding:utf-8 -*- class Solution: def multiply(self, A): # write code here # 計算上三角形 length = len(A) B=[None]*length B[0]=1 for i in range(1,length): B[i] = B[i-1]*A[i-1] temp = 1 n = length-2 while n>=0: temp *= A[n+1] B[n] *= temp n-=1 return B
3.二維數組中的查找
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
方法1:順序查找
def Find(self, target, array): # write code here if len(array)==0 or len(array[0])==0: return 0 l=0 r=len(array[0])-1 for i in range(len(array)): if array[i][0]<=target and targe<=array[i][-1]: for j in range(len(array[i])): if array[i][j]==target: return True return False
方法2:二分查找
def Find(self, target, array): # write code here if len(array)==0 or len(array[0])==0: return 0 for i in range(len(array)): if array[i][0]<=target<=array[i][-1]: l=0 r=len(array[0])-1 while(l<=r): mid=l+(r-l)//2 if array[i][mid]<target : l=mid+1 elif array[i][mid]>target: r=mid-1 else: return True return False
附:繼續分享一個思路通透的
題目描述
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
1
2
3
4
|
1
2
8
9
2
4
9
12
4
7
10
13
6
8
11
15
|
思路
無意間翻看同學桌上的《劍指 Offer》,看到了這道題,我隱約覺得應該使用二分查找。考慮了一番想到了如下解決方案。
考慮上面的例子,如果我要查找 10,通過觀察最右邊的哪一列,我就知道第一行是可以排除的,因為第一行中最大的元素還小於 10。同理觀察最后一行,我可以發現前兩列是可以排除的,因為這兩列最大的元素還小於 10。因此,余下的就是下面這些了:
1
2
3
|
9
12
10
13
11
15
|
我再觀察第 1 列,發現最后一行是可以排除的,因為最后一行最小的數 11 是大於 10 的。同理,我觀察第 1 行,發現最后一列是可以排除的,因為這一列中最小的元素 12 大於 10。這樣以來就只余下這些了:
1
2
|
9
10
|
基於以上發現,可以給行和列維護一個范圍。交替地在余下的矩陣的左上方和右下方做二分查找,來縮小范圍。下面是我的實現:
import numpy as np def upper_bound(a, x, lo=0, hi=None): """ find the first element great than x in a sorted list return the index of this element """ if hi is None: hi = len(a) while lo < hi: mid = (lo + hi) // 2 if a[mid] <= x: lo = mid + 1 else: hi = mid return lo def lower_bound(a, x, lo=0, hi=None): """ find the first element that is not less than x (i.e. greater or equal to) """ if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 if a[mid] < x: lo = mid + 1 else: hi = mid return lo class Solution: def Find(self, matrix, x): if len(matrix) == 0 or len(matrix[0]) == 0: return False matrix = np.array(matrix) row_lo, row_hi = 0, matrix.shape[0] col_lo, col_hi = 0, matrix.shape[1] while True: row_lo = lower_bound(matrix[:, col_hi-1], x, row_lo, row_hi) col_lo = lower_bound(matrix[row_hi-1], x, col_lo, col_hi) if row_lo == row_hi or col_lo == col_hi: break row_hi = upper_bound(matrix[:, col_lo], x, row_lo, row_hi) col_hi = upper_bound(matrix[row_lo], x, col_lo, col_hi) if row_lo == row_hi or col_lo == col_hi: break if matrix[row_lo, col_lo] == x or matrix[row_lo, col_hi-1] == x: return True if matrix[row_hi-1, col_lo] == x or matrix[row_hi-1, col_hi-1] == x: return True return False
or
# -*- coding:utf-8 -*- class Solution: # array 二維列表 def Find(self, target, array): # write code here #方法二 從右上角開始進行搜索 if not array: return False rows = len(array) columns = len(array[0]) row = 0 column = columns - 1 while row < rows and column >= 0: if array[row][column] == target: return True if array[row][column] > target: column -= 1 else: row += 1 return False
or 暴力搜索:
#暴力搜索
for
arr in array:
if
target in arr:
return
True
return
False