經(jīng)典算法問(wèn)題:數(shù)組中的逆序?qū)?/h1>

《劍指 Offer》(第 2 版)第 51 題:數(shù)組中的逆序?qū)?/h2>

傳送門:數(shù)組中的逆序?qū)?/a>。

要求:在數(shù)組中的兩個(gè)數(shù)字如果前面一個(gè)數(shù)字大于后面的數(shù)字汰规,則這兩個(gè)數(shù)字組成一個(gè)逆序?qū)Α?/p>

輸入一個(gè)數(shù)組访圃,求出這個(gè)數(shù)組中的逆序?qū)Φ目倲?shù)姨裸。

樣例:

輸入:[1,2,3,4,5,6,0]

輸出:6

思路1:使用定義

挨個(gè)數(shù)出來(lái)悲敷,使用定義計(jì)算逆序數(shù)覆履。不過(guò)時(shí)間復(fù)雜度是 O(n^2)蹋盆。

class Solution(object):
    def inversePairs(self, nums):
        l = len(nums)
        if l < 2:
            return 0
        res = 0
        for i in range(0, l - 1):
            for j in range(i + 1, l):
                if nums[i] > nums[j]:
                    res += 1
        return res

這種思路雖然很直接费薄,但編寫出錯(cuò)的概率很低,在沒(méi)有在線評(píng)測(cè)系統(tǒng)的時(shí)候栖雾,它可以作為一個(gè)“正確的”參考答案楞抡,用以檢驗(yàn)我們自己編寫的算法是否正確。

思路2:分治

這道題最經(jīng)典的思路是使用分治法計(jì)算析藕,借助“歸并排序”的分治思想召廷,排好序以后,逆序?qū)颓蟪鰜?lái)了账胧,時(shí)間復(fù)雜度為 O(n \log n)竞慢。下面舉例說(shuō)明:例如:前有序數(shù)組:[2,3,5,8],后有序數(shù)組:[4,6,7,12]治泥。

做歸并的時(shí)候筹煮,步驟如下:

第 1 步,2 先出列居夹,2 比“后有序數(shù)組”中所有的元素都小败潦,構(gòu)成“順序?qū)Α保?/p>

第 2 步,3 出列准脂,3 比“后有序數(shù)組”中所有的元素都小劫扒,構(gòu)成“順序?qū)Α保?/p>

第 3 步,4 出列狸膏,關(guān)鍵的地方在這里沟饥,“前有序數(shù)組”中所有剩下的元素 [5,8]4 都大,構(gòu)成 2 個(gè) “逆序?qū)Α?/strong>环戈;

第 4 步闷板,5 出列,5 比“后有序數(shù)組”中所有剩下的元素都小院塞,構(gòu)成“順序?qū)Α保?/p>

第 5 步遮晚,6 出列,“前有序數(shù)組”中所有剩下的元素 [8]6 都大拦止,構(gòu)成 1 個(gè)“逆序?qū)Α?/strong>县遣;

第 6 步,7 出列汹族,“前有序數(shù)組”中所有剩下的元素 [8]7 都大萧求,構(gòu)成 1 個(gè)“逆序?qū)Α?/strong>;

第 7 步顶瞒,8 出列夸政,8 比“后有序數(shù)組”中所有剩下的元素 [8] 都小,構(gòu)成 1 個(gè)“順序?qū)Α保?/p>

第 8 步榴徐,12 出列守问,此時(shí)“前有序數(shù)組”為空匀归。

因此,我們只需要在“前有序數(shù)組”非空耗帕,且“后有序數(shù)組”中有元素出列的時(shí)候穆端,即上面的第 3、5仿便、6 步計(jì)算“逆序?qū)Α本涂梢粤恕?/p>

Python 代碼:

class Solution(object):
    def inversePairs1(self, nums):
        l = len(nums)
        if l < 2:
            return 0
        res = 0
        for i in range(0, l - 1):
            for j in range(i + 1, l):
                if nums[i] > nums[j]:
                    res += 1
        return res

    def inversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        l = len(nums)
        if l < 2:
            return 0
        temp = [0 for _ in range(l)]
        return self.count_inversion_pairs(nums, 0, l - 1, temp)

    def count_inversion_pairs(self, nums, l, r, temp):
        """
        在數(shù)組 nums 的區(qū)間 [l,r] 統(tǒng)計(jì)逆序?qū)?        :param nums:
        :param l: 待統(tǒng)計(jì)數(shù)組的左邊界体啰,可以取到
        :param r: 待統(tǒng)計(jì)數(shù)組的右邊界,可以取到
        :param temp:
        :return:
        """
        # 極端情況下嗽仪,就是只有 1 個(gè)元素的時(shí)候
        if l == r:
            return 0
        mid = l + (r - l) // 2
        left_pairs = self.count_inversion_pairs(nums, l, mid, temp)
        right_pairs = self.count_inversion_pairs(nums, mid + 1, r, temp)

        merge_pairs = 0
        # 代碼走到這里的時(shí)候荒勇,
        # [l, mid] 已經(jīng)完成了排序并且計(jì)算好逆序?qū)?        # [mid + 1, r] 已經(jīng)完成了排序并且計(jì)算好逆序?qū)?        # 如果 nums[mid] <= nums[mid + 1],此時(shí)就不存在逆序?qū)?        # 當(dāng) nums[mid] > nums[mid + 1] 的時(shí)候闻坚,就要繼續(xù)計(jì)算逆序?qū)?        if nums[mid] > nums[mid + 1]:
            # 在歸并的過(guò)程中計(jì)算逆序?qū)?            merge_pairs = self.merge_and_count(nums, l, mid, r, temp)
        # 走到這里有 nums[mid] <= nums[mid + 1] 成立枕屉,已經(jīng)是順序結(jié)構(gòu)
        return left_pairs + right_pairs + merge_pairs

    def merge_and_count(self, nums, l, mid, r, temp):
        """
        前:[2,3,5,8],后:[4,6,7,12]
        我們只需要在后面數(shù)組元素出列的時(shí)候鲤氢,數(shù)一數(shù)前面這個(gè)數(shù)組還剩下多少個(gè)數(shù)字,
        因?yàn)?前"數(shù)組和"后"數(shù)組都有序西潘,
        因此卷玉,"前"數(shù)組剩下的元素個(gè)數(shù) mid - i + 1 就是與"后"數(shù)組元素出列的這個(gè)元素構(gòu)成的逆序?qū)€(gè)數(shù)
         
        """
        for i in range(l, r + 1):
            temp[i] = nums[i]
        i = l
        j = mid + 1
        res = 0
        for k in range(l, r + 1):
            if i > mid:
                nums[k] = temp[j]
                j += 1
            elif j > r:
                nums[k] = temp[i]
                i += 1
            elif temp[i] <= temp[j]:
                # 不統(tǒng)計(jì)逆序?qū)Γ蛔雠判?                nums[k] = temp[i]
                i += 1
            else:
                assert temp[i] > temp[j]
                nums[k] = temp[j]
                j += 1
                # 快就快在這里喷市,一次可以數(shù)出一個(gè)區(qū)間的個(gè)數(shù)的逆序?qū)?                # 例:[7,8,9][4,6,9]相种,4 與 7 以及 7 前面所有的數(shù)都構(gòu)成逆序?qū)?                res += (mid - i + 1)
        return res

說(shuō)明:歸并兩個(gè)有序數(shù)組的時(shí)候,我們要借助額外的輔助空間品姓,為此可以全局使用一個(gè)和原始數(shù)組等長(zhǎng)的輔助數(shù)組寝并,否則每一次進(jìn)入 merge 函數(shù)都要 new 新數(shù)組,開(kāi)銷很大腹备。

上述解法的缺點(diǎn)是修改了原始數(shù)組衬潦,排序完成以后,逆序數(shù)就計(jì)算出來(lái)了植酥。為此:(1)我們可以引入一個(gè)索引數(shù)組镀岛;(2)或者直接拷貝一個(gè)原始數(shù)組,這樣就不用修改原始數(shù)組了友驮。

思路3:使用“樹(shù)狀數(shù)組”

關(guān)于樹(shù)狀數(shù)組漂羊,我寫了一篇文章,在這里卸留,體會(huì)什么是“離散化”走越。

class Solution(object):
    def inversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        class FenwickTree:
            def __init__(self, n):
                self.size = n
                self.tree = [0 for _ in range(n + 1)]

            def __lowbit(self, index):
                return index & (-index)

            # 單點(diǎn)更新:從下到上,最多到 size耻瑟,可以取等
            def update(self, index, delta):
                while index <= self.size:
                    self.tree[index] += delta
                    index += self.__lowbit(index)

            # 區(qū)間查詢:從上到下旨指,最少到 1赏酥,可以取等
            def query(self, index):
                res = 0
                while index > 0:
                    res += self.tree[index]
                    index -= self.__lowbit(index)
                return res

        # 特判
        l = len(nums)
        if l < 2:
            return 0

        # 原始數(shù)組去除重復(fù)以后從小到大排序
        s = list(set(nums))

        # 構(gòu)建最小堆,因?yàn)閺男〉酱笠粋€(gè)一個(gè)拿出來(lái)淤毛,用堆比較合適
        import heapq
        heapq.heapify(s)

        # 由數(shù)字查排名
        rank_map = dict()
        index = 1
        # 不重復(fù)數(shù)字的個(gè)數(shù)
        size = len(s)
        for _ in range(size):
            num = heapq.heappop(s)
            rank_map[num] = index
            index += 1

        res = 0
        # 樹(shù)狀數(shù)組只要不重復(fù)數(shù)字個(gè)數(shù)這么多空間就夠了
        ft = FenwickTree(size)
        # 從后向前看今缚,拿出一個(gè)數(shù)字來(lái),就更新一下低淡,然后向前查詢比它小的個(gè)數(shù)
        for i in range(l - 1, -1, -1):
            rank = rank_map[nums[i]]
            ft.update(rank, 1)
            res += ft.query(rank - 1)
        return res

說(shuō)明:中間將數(shù)字映射到排名是將原數(shù)組“離散化”姓言,“離散化”的原因有 2 點(diǎn):

1、樹(shù)狀數(shù)組我們看到蔗蹋,索引是從“1”開(kāi)始的何荚,我們不能保證我們的數(shù)組所有的元素都大于等于 1

2猪杭、即使元素都大于等于“1”餐塘,為了節(jié)約樹(shù)狀數(shù)組的空間,我們將之“離散化”可以把原始的數(shù)都?jí)嚎s到一個(gè)小區(qū)間皂吮。我說(shuō)的有點(diǎn)不太清楚戒傻,這一點(diǎn)可以參考 樹(shù)狀數(shù)組 求逆序數(shù) poj 2299

LeetCode 第 315 題:計(jì)算右側(cè)小于當(dāng)前元素的個(gè)數(shù)

傳送門:315. 計(jì)算右側(cè)小于當(dāng)前元素的個(gè)數(shù)蜂筹。

給定一個(gè)整數(shù)數(shù)組 nums需纳,按要求返回一個(gè)新數(shù)組 counts。數(shù)組 counts 有該性質(zhì): counts[i] 的值是 nums[i] 右側(cè)小于 nums[i] 的元素的數(shù)量艺挪。

示例:

輸入: [5,2,6,1]
輸出: [2,1,1,0] 
解釋:
5 的右側(cè)有 2 個(gè)更小的元素 (2 和 1).
2 的右側(cè)僅有 1 個(gè)更小的元素 (1).
6 的右側(cè)有 1 個(gè)更小的元素 (1).
1 的右側(cè)有 0 個(gè)更小的元素.

解法1:自定義 BST 的解法不翩,比較巧妙,參考:

https://segmentfault.com/a/1190000008233819

【算法】逆序?qū)?wèn)題的四種解法(歸并排序麻裳,BST口蝠,樹(shù)狀數(shù)組,線段樹(shù))及變形
https://blog.csdn.net/haolexiao/article/details/54989306

leetcode 315 Count of Smaller Numbers After Self 以及 BST總結(jié)津坑。
https://segmentfault.com/a/1190000008233783

image-20190116233732944

Python 代碼:

class Solution:

    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """

        class TreeNode:
            def __init__(self, val):
                self.left = None
                self.right = None
                self.val = val
                self.left_node_sum = 0
                self.duplicate_times = 1

        def insert(node, val, index, cum_left_nodeSum, counter):
            if node is None:
                node = TreeNode(val)
                counter[index] = cum_left_nodeSum
                return node
            if node.val == val:
                node.duplicate_times += 1
                counter[index] = cum_left_nodeSum + node.left_node_sum
            elif node.val > val:
                node.left_node_sum += 1
                node.left = insert(node.left, val, index, cum_left_nodeSum, counter)
            else:
                node.right = insert(node.right, val, index,
                                    cum_left_nodeSum + node.duplicate_times + node.left_node_sum, counter)
            return node

        l = len(nums)
        if l == 0:
            return []
        if l == 1:
            return [0]

        # 從后向前填表
        res = [None for _ in range(l)]
        root = None
        for index in range(l - 1, -1, -1):
            root = insert(root, nums[index], index, 0, res)
        print(root)
        return res

樹(shù)狀數(shù)組 求逆序數(shù) poj 2299
https://blog.csdn.net/u013445530/article/details/39829053

把離散化搞清楚

poj 2299 Ultra-QuickSort 求逆序數(shù)妙蔗,樹(shù)狀數(shù)組解法,詳細(xì)解析
https://blog.csdn.net/Lionel_D/article/details/43535741

白話數(shù)據(jù)結(jié)構(gòu)
https://blog.csdn.net/column/details/acxxz.html

(本節(jié)完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者

  • 序言:七十年代末疆瑰,一起剝皮案震驚了整個(gè)濱河市灭必,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乃摹,老刑警劉巖禁漓,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異孵睬,居然都是意外死亡播歼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秘狞,“玉大人叭莫,你說(shuō)我怎么就攤上這事∷甘裕” “怎么了雇初?”我有些...
    開(kāi)封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)减响。 經(jīng)常有香客問(wèn)我靖诗,道長(zhǎng),這世上最難降的妖魔是什么支示? 我笑而不...
    開(kāi)封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任刊橘,我火速辦了婚禮,結(jié)果婚禮上颂鸿,老公的妹妹穿的比我還像新娘促绵。我一直安慰自己,他們只是感情好嘴纺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布败晴。 她就那樣靜靜地躺著,像睡著了一般栽渴。 火紅的嫁衣襯著肌膚如雪位衩。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天熔萧,我揣著相機(jī)與錄音,去河邊找鬼僚祷。 笑死佛致,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辙谜。 我是一名探鬼主播俺榆,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼装哆!你這毒婦竟也來(lái)了罐脊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜕琴,失蹤者是張志新(化名)和其女友劉穎萍桌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凌简,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡上炎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雏搂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藕施。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寇损,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裳食,到底是詐尸還是另有隱情矛市,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布诲祸,位于F島的核電站浊吏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烦绳。R本人自食惡果不足惜卿捎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望径密。 院中可真熱鬧午阵,春花似錦、人聲如沸享扔。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惧眠。三九已至籽懦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氛魁,已是汗流浹背暮顺。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秀存,地道東北人捶码。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像或链,于是被迫代替她去往敵國(guó)和親惫恼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容