[Python] 算法心得—數(shù)組篇

1 尋找最大的k個數(shù)

輸入包含n個整數(shù)的數(shù)組丘喻,輸出其中最大的k個數(shù)。
要求:輸出的數(shù)字不能重復,如果k大于可輸出數(shù)字的個數(shù)撮执,便輸出該數(shù)組從大到小的不重復排列。

如:
輸入數(shù)組 nums=[2,1,3,3,4,4,5] 和 k=3汽馋,則輸出[5,4,3];
輸入數(shù)組 nums=[2,1,3,3,4,4,5] 和 k=100纺讲,則輸出 [5,4,3,2,1]未檩。

解法一:暴力排列法

直接將整個數(shù)組排序蕾总,取最后k位粥航,顯然寫起來簡單,但是總的時間復雜高生百。比如使用快速排序递雀,然后再遍歷序列中前k個元素,總的復雜度達到 O(n * log n) + O(k) = O(n * log n)蚀浆。

實現(xiàn)代碼:

def max_k(nums, k):
    nums = sorted(list(set(nums)))[::-1]
    return nums[-k:]
解法二:排列k個數(shù)

仔細想想缀程,我們并沒有必要對整個數(shù)組排序搜吧。假設k給定為3,也就是說要返回最大的三個數(shù)杨凑,我們可以采取以下代碼實現(xiàn):

def max_three(nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    max1 = -sys.maxsize - 1
    max2 = -sys.maxsize - 1
    max3 = -sys.maxsize - 1
    nums = list(set(nums))
    for i in range(len(nums)):
        if nums[i] > max1:
            max3 = max2
            max2 = max1
            max1 = nums[i]

        elif nums[i] > max2:
            max3 = max2
            max2 = nums[i]

        elif nums[i] > max3:
            max3 = nums[i]
    if max3 != (-sys.maxsize - 1):
        return max3
    else:
        return max1

即定義三個變量(初始值為最小整數(shù))滤奈,然后遍歷數(shù)組中的元素,保證這三個變量是當前遍歷的最大的三個元素撩满。此時的時間復雜度為O(n)蜒程。

那么如果k是變量呢?我們可以采用同樣的思路鹦牛,定義一個長度為k+1的數(shù)組(每個元素初始值均為最小整數(shù))搞糕,遍歷數(shù)組勇吊,將元素插入數(shù)組中合適的位置曼追,時間復雜度為 n * O(k) = O(nk) 具體實現(xiàn)代碼如下。

import sys


def max_k(nums, k):
    arr_k = [-sys.maxsize - 1] * (k + 1)
    nums_set = list(set(nums))
    for i in range(len(nums_set)):
        if nums_set[i] > arr_k[k]:
            for j in range(k):
                if arr_k[j] < nums_set[i]:
                    arr_k.insert(j, nums_set[i])
                    break
    return arr_k[:min(k, len(nums_set))]
解法三:排列k個數(shù)(更優(yōu)化)

參考一下快速排序的思想汉规,在一個數(shù)組中選擇一個基準點礼殊,然后將所有小于該基準點的數(shù)放置在基準點左邊,所有大于該基準點的數(shù)放置在基準點右邊针史,隨后左右兩個子數(shù)組繼續(xù)尋找新的基準點不斷遞歸晶伦,知道每個子數(shù)組均只包含一個數(shù)字。
那么我們可以將基準點設在k位置處啄枕,并且只遞歸k右邊的子數(shù)組婚陪,時間復雜度為 O(k * log k) 具體代碼實現(xiàn)如下:

def max_k(nums, k):
    my_list = list(set(nums))

    return solution(my_list, k)


def solution(my_list, k, start=None, end=None, pivot_k=False):
    start = 0 if start is None else start
    end = len(my_list) - 1 if end is None else end

    pivot_k = False if (k>len(my_list) and pivot_k) else pivot_k

    if start < end:
        i, j = start, end
        point = k if pivot_k else i
        while i != j:
            while my_list[j] >= my_list[point] and i < j:
                j = j - 1
            while my_list[i] <= my_list[point] and i < j:
                i = i + 1
            my_list[i], my_list[j] = my_list[j], my_list[i]
            if i == j:
                my_list[i], my_list[point] = my_list[point], my_list[i]
            if not pivot_k:
                solution(my_list, k, start, i - 1, pivot_k=False)
            solution(my_list, k, i + 1, end, pivot_k=False)

    return my_list[::-1][:k]

2.1 尋找和為定值的兩個數(shù)

輸入一個數(shù)組和一個數(shù)字,在數(shù)組中查找兩個數(shù)频祝,使得它們的和正好是輸入的那個數(shù)字泌参。
例如輸入數(shù)組1、2常空、4沽一、7、11漓糙、15和數(shù)字15铣缠。由于4+11=15,因此輸出4和11昆禽。

解法一:暴力查找法

直接窮舉蝗蛙,從數(shù)組中任意選兩個數(shù)字,判斷它們的和是否為給定數(shù)字醉鳖,時間復雜度為O(N^2)捡硅。
或者我們也可以使用逆向思維,用給定數(shù)字target減去數(shù)組中的元素辐棒,判斷結果是否存在于數(shù)組中病曾,雖然時間復雜度仍為O(n^2)牍蜂,但是代碼實現(xiàn)比較方便:

def two_sum(nums, target):
    for x in nums:
        if (target - x) in nums:
           return [x, target - x]
解法二:構造哈希映射

通過構造哈希映射的方法,可以給定一個數(shù)字泰涂,根據(jù)哈希映射查找另一個數(shù)字是否也在數(shù)組中鲫竞,只需用O(1)的時間,前提是經(jīng)過O(N)時間的預處理逼蒙,以及用O(N)的空間構造哈希表从绘。

def two_sum(nums, target):
    nums = list(set(nums))
    mapper = {}
    for i in range(len(nums)):
        if nums[i] in mapper.values():
            return [nums[i], target - nums[i]]
        mapper[nums[i]] = target - nums[i]
解法三:二分查找

首先將數(shù)組進行排序,花費 O(N * log N)復雜度是牢;隨后遍歷數(shù)組僵井,對目標數(shù)減去每個元素進行二分查找,花費 N * O(log N) = O(N * log N)驳棱,具體代碼如下:

def two_sum(nums, target):
    nums = sorted(nums)
    for i in range(len(nums)):
        l, r = i + 1, len(nums) - 1
        tmp = target - nums[i]
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == tmp:
                return [nums[i + 1], nums[mid + 1]]
            elif nums[mid] < tmp:
                l = mid + 1
            else:
                r = mid - 1
解法四:雙指針查找

首先將數(shù)組排序批什,隨后定義兩個指針,分別指向數(shù)組首位兩端社搅,隨后進行迭代:

  • 如果nums[i] + nums[j] > target驻债,則需要讓兩者和減少,所以i不變形葬,j--合呐;
  • 如果nums[i] + nums[j] < target,則需要讓兩者和增大笙以,所以j不變淌实,i--;
  • 如果nums[i] + nums[j] == target猖腕,跳出循環(huán)拆祈,返回nums[i]和nums[j]。

該算法的時間復雜度為 `O(N * log N + N) = O(N * log N)谈息,實現(xiàn)代碼如下:

def two_sum(nums, target):
    l, r = 0, len(nums) - 1
    while l < r:
        if nums[l] + nums[r] == target:
            return [l + 1, r + 1]
        elif nums[l] + nums[r] < target:
            l += 1
        else:
            r -= 1

2.2 擴展:三數(shù)和

Leetcode 15題:給出一個數(shù)組缘屹,要求判斷該數(shù)組中是否有三個數(shù)的和為0,并返回全部符合要求并且不重復的三個數(shù)侠仇,這里用了三個指針轻姿。

def three_sum(nums):
    result = []
    if len(nums) < 3:
        return result
    nums.sort()
    i = 0
    while i < len(nums) - 2:
        l, r = i + 1, len(nums) - 1
        b = nums[i]
        while l < r:
            a, c = nums[l], nums[r]
            if nums[l] + nums[i] + nums[r] == 0:
                result.append([nums[i], nums[l], nums[r]])
                while l < r and a == nums[l]:
                    l += 1
                while l < r and c == nums[r]:
                    r -= 1
            elif nums[l] + nums[i] + nums[r] < 0:
                l += 1
            else:
                r -= 1

        while i < len(nums) and b == nums[i]:
            i += 1

    return result

2.3 擴展:判斷二叉樹中有和為定值的根葉路徑

Leetcode 112題:給定一個二叉樹與一個給定值,判斷二叉樹中是否有根到葉和為該給定值的路徑逻炊。

def has_path_sum(root, sum):
    if not root:
        return False

    if not root.left and not root.right and root.val == sum:
        return True

    sum -= root.val

    return has_path_sum(root.left, sum) or has_path_sum(root.right, sum)

3.1 最大連續(xù)子數(shù)組和

輸入一個元素全為整數(shù)(有正有負)的數(shù)組互亮,數(shù)組中連續(xù)的一個或多個整數(shù)組成一個子數(shù)組,每個子數(shù)組都有一個和余素,求所有子數(shù)組和的最大值豹休。

解法一: 暴力求解法

三個for循環(huán)三層遍歷,求出數(shù)組中每一個子數(shù)組的和桨吊,最終求出這些子數(shù)組的最大的一個值威根,時間復雜度為O(n^3)凤巨。

def max_subarray_c(arr):
    max_sum = a[0]
    cur_sum = 0
    n = len(arr)
    for i in range(n):
        for j in range(i, n):
            for k in range(i, j+1):
                cur_sum += arr[k]
            if cur_sum > max_sum:
                max_sum = cur_sum
            cur_sum = 0
    return max_sum
解法二: 分治策略

首先找出數(shù)組中點mid = (low + high) // 2,隨后將數(shù)組分為左右兩部分洛搀。

尋找到的最大子數(shù)組arr[i,j+1]有三種情況:

  • 完全位于數(shù)組左部分敢茁,即數(shù)組arr[low...mid]中,low<=i<=j<=mid留美;
  • 完全位于數(shù)組右部分彰檬,即數(shù)組arr[mid...high]中,mid<i<=j<=high谎砾;
  • 跨越了中點逢倍,即low<=i<=mid<j<=high

我們可以遞歸求解arr[low...mid]和arr[mid+1...high]的最大子數(shù)組,剩下的工作就是尋找跨越重點的最大子數(shù)組景图,并比較這三種情況中的最大者较雕,時間復雜度為O(n * log n)。

def max_cross_subarray(arr, mid, low, high):
    now_left = arr[mid]
    left = arr[mid]
    for i in range(mid - 1, low - 1, -1):
        now_left = now_left + arr[i]
        if now_left > left:
            left = now_left
    now_right = arr[mid + 1]
    right = arr[mid + 1]
    for j in range(mid + 2, high + 1):
        now_right = now_right + arr[j]
        if now_right > right:
            right = now_right
    return left + right


def max_subarray(arr, low, high):
    if high == low:
        return arr[low]
    else:
        mid = (low + high) // 2
        m1 = max_subarray(arr, low, mid)
        m2 = max_subarray(arr, mid + 1, high)

        m3 = max_cross_subarray(arr, mid, low, high)

        result = max(m1, m2, m3)
        return result
解法三: 迭代更新

假設cur_sum為當前最大子數(shù)組的和症歇,max_sum為最后要返回的最大子數(shù)組的和郎笆,當我們進行迭代時:

  • 對第j+1個元素有兩種選擇谭梗,要么放入前面找到的子數(shù)組忘晤,要么作為新子數(shù)組的第一個元素:

  • 如果cur_sum加上當前元素arr[i]后不小于arr[i],則令cur_sum加上arr[i]激捏,否則cur_sum重新賦值為arr[i]设塔。

  • 同時,當cur_sum > max_sum远舅,則更新max_sum = cur_sum闰蛔,否則保持原值,不更新图柏。

def max_subarray(arr):
    if not arr:
        return 0

    cur_sum = max_sum = arr[0]
    for num in arr[1:]:
        cur_sum = max(num, cur_sum + num)
        max_sum = max(max_sum, cur_sum)

    return max_sum

4.1 最大連續(xù)乘積子串

給一個浮點數(shù)序列序六,取最大乘積連續(xù)子串的值,例如:[-2.5, 4, 0, 3, 0.5, 8, -1]蚤吹,則取出的最大乘積連續(xù)子串為[3, 0.5, 8]例诀。

解法一:暴力遍歷法

簡單粗暴,兩個for循環(huán)裁着,時間復雜度O(N^2):

def max_product_substring(a):
    max_res = a[0]
    for i in range(len(a)):
        x = 1
        for j in range(i, len(a)):
            x *= a[j]
            if x > max_res:
                max_res = x
    return max_res
解法二:迭代更新

這里仿照2.3.1中的解法繁涂,但需要注意的是,乘積子序列有正有負二驰,我們可以同時找乘積最大與乘積最小的值(因為最小的負值有可能與另一個負值做乘積時成了最大的正值)扔罪。

def max_product_substring(a):
    max_end = a[0]
    min_end = a[0]
    max_res = a[0]
    for i in range(1, len(a)):
        end1 = max_end * a[i]
        end2 = min_end * a[i]
        max_end = max(max(end1, end2), a[i])
        min_end = min(min(end1, end2), a[i])
        max_res = max(max_res, max_end)
    return max_res
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市桶雀,隨后出現(xiàn)的幾起案子矿酵,更是在濱河造成了極大的恐慌唬复,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件全肮,死亡現(xiàn)場離奇詭異盅抚,居然都是意外死亡,警方通過查閱死者的電腦和手機倔矾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門妄均,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哪自,你說我怎么就攤上這事丰包。” “怎么了壤巷?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵邑彪,是天一觀的道長。 經(jīng)常有香客問我胧华,道長寄症,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任矩动,我火速辦了婚禮有巧,結果婚禮上,老公的妹妹穿的比我還像新娘悲没。我一直安慰自己篮迎,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布示姿。 她就那樣靜靜地躺著甜橱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栈戳。 梳的紋絲不亂的頭發(fā)上岂傲,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音子檀,去河邊找鬼镊掖。 笑死,一個胖子當著我的面吹牛命锄,可吹牛的內(nèi)容都是我干的堰乔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼脐恩,長吁一口氣:“原來是場噩夢啊……” “哼镐侯!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苟翻,失蹤者是張志新(化名)和其女友劉穎韵卤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇猫,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡沈条,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诅炉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜡歹。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涕烧,靈堂內(nèi)的尸體忽然破棺而出月而,到底是詐尸還是另有隱情,我是刑警寧澤议纯,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布父款,位于F島的核電站,受9級特大地震影響瞻凤,放射性物質(zhì)發(fā)生泄漏憨攒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一阀参、第九天 我趴在偏房一處隱蔽的房頂上張望肝集。 院中可真熱鬧,春花似錦结笨、人聲如沸包晰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至勉痴,卻和暖如春赫模,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒸矛。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工瀑罗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雏掠。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓斩祭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乡话。 傳聞我的和親對象是個殘疾皇子摧玫,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355