LeetCode算法題-排序類


1.兩個數組的交集2
給定兩個數組,編寫一個函數來計算它們的交集。
 
示例 1:
輸入: nums1 = [1,2,2,1], nums2 = [2,2]
輸出: [2,2]
示例 2:
 
輸入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
輸出: [4,9]
說明:
 
輸出結果中每個元素出現的次數,應與元素在兩個數組中出現的次數一致。
我們可以不考慮輸出結果的順序。
進階:
 
如果給定的數組已經排好序呢?你將如何優化你的算法?
如果 nums1 的大小比 nums2 小很多,哪種方法更優?
如果 nums2 的元素存儲在磁盤上,磁盤內存是有限的,並且你不能一次加載所有的元素到內存中,你該怎么辦?
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii
 
關鍵點和易錯點:
(1)比較直觀的是想到用兩個字典去記錄來做,但其實只要使用一個字典就夠了,而且還可以判斷下那個列表短就用哪個哈希到字典里,節省內存
 
提交代碼:
class Solution(object):
    def intersect(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: List[int]
        """
        
        if len(nums1) > len(nums2):
            return self.intersect(nums2, nums1)
            
        hash_map = {}
        for num in nums1:
            if num not in hash_map:
                hash_map[num] = 1
            else:
                hash_map[num] += 1
                
        result = []
        for num in nums2:
            if num in hash_map and hash_map[num] > 0:
                result.append(num)
                hash_map[num] -= 1
            
        return result

 

2.合並區間
給出一個區間的集合,請合並所有重疊的區間。
 
示例 1:
輸入: [[1,3],[2,6],[8,10],[15,18]]
輸出: [[1,6],[8,10],[15,18]]
解釋: 區間 [1,3] 和 [2,6] 重疊, 將它們合並為 [1,6].
示例 2:
 
輸入: [[1,4],[4,5]]
輸出: [[1,5]]
解釋: 區間 [1,4] 和 [4,5] 可被視為重疊區間。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/merge-intervals
 
關鍵點和易錯點:
 
提交代碼:
class Solution(object):
    def merge(self, intervals):
        """
        :type intervals: List[List[int]]
        :rtype: List[List[int]]
        """
        
        intervals.sort(key=lambda x: x[0])
        
        result = [intervals[0]] if len(intervals) > 0 else []
        
        n = len(intervals)
        for i in xrange(1, n):
            if intervals[i][0] <= result[-1][1]:
                if intervals[i][1] > result[-1][1]:
                    result[-1][1] = intervals[i][1]
            else:
                result.append(intervals[i])
                
        return result

 

3.插入區間
給出一個無重疊的 ,按照區間起始端點排序的區間列表。
在列表中插入一個新的區間,你需要確保列表中的區間仍然有序且不重疊(如果有必要的話,可以合並區間)。
 
示例 1:
輸入: intervals = [[1,3],[6,9]], newInterval = [2,5]
輸出: [[1,5],[6,9]]
示例 2:
 
輸入: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
輸出: [[1,2],[3,10],[12,16]]
解釋: 這是因為新的區間 [4,8] 與 [3,5],[6,7],[8,10] 重疊。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/insert-interval
 
關鍵點和易錯點:
(1)這道題本身是挺簡單的,但我一開始想復雜了,一開始思路就朝着二分法去做,雖然會優點,但程序邊界變得很復雜,得不償失,容易出錯
(2)常規法的for循環那里被坑了,for循環遍歷來出來后它的i還是小於n的,只是i+1==n了,這點跟c不一樣,被坑了好幾次了
(3)盡量不要用list的remove操作,因為相當於拷貝數組操作,時間復雜度很高,影響速度,寧願另開一個list來保存
 
常規法:
class Solution(object):
    def insert(self, intervals, newInterval):
        """
        :type intervals: List[List[int]]
        :type newInterval: List[int]
        :rtype: List[List[int]]
        """
        
        n = len(intervals)
        result = []
        
        flag = True
        for i in xrange(0, n):
            if flag:
                if newInterval[1] < intervals[i][0]:
                    flag = False
                    result.append(newInterval)
                    result.append(intervals[i])
                elif newInterval[0] > intervals[i][1]:
                    result.append(intervals[i])
                else:
                    result.append([min(intervals[i][0], newInterval[0]), max(intervals[i][1], newInterval[1])])
                    flag = False
            else:
                if intervals[i][0] <= result[-1][1]:
                    result[-1][1] = max(intervals[i][1], result[-1][1])
                else:
                    result.append(intervals[i])
                    break
                    
        if flag:
            result.append(newInterval)
        else:
            result.extend(intervals[i+1:])
            
        return result

 

二分法:

class Solution(object):
    def insert(self, intervals, newInterval):
        """
        :type intervals: List[List[int]]
        :type newInterval: List[int]
        :rtype: List[List[int]]
        """
        
        n = len(intervals)
        result = []
        
        def find_max_less(index):
            i = 0
            j = n-1
            while i <= j:
                mid = (i+j)/2
                if intervals[mid][0] == newInterval[index]:
                    return mid
                elif intervals[mid][0] > newInterval[index]:
                    j = mid - 1
                else:
                    if mid+1 >= n or intervals[mid+1][0] > newInterval[index]:
                        return mid
                    else:
                        i = mid + 1
                        
            return -1
            
        start = find_max_less(0)
        end = find_max_less(1)
        
        if start == end:
            if start == -1:
                intervals.insert(0, newInterval)
            elif newInterval[0] > intervals[start][1]:
                intervals.insert(start+1, newInterval)
            else:
                intervals[start][1] = max(intervals[start][1], newInterval[1])
        else:
            if start == -1 or newInterval[0] > intervals[start][1]:
                start += 1
                intervals[start][0] = newInterval[0]
            intervals[start][1] = max(intervals[end][1], newInterval[1])
            
            result.extend(intervals[0:start+1])
            result.extend(intervals[end+1:])
                
        return result if result else intervals

 

4.顏色分類
給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。
 
此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。
 
注意:
不能使用代碼庫中的排序函數來解決這道題。
 
示例:
 
輸入: [2,0,2,1,1,0]
輸出: [0,0,1,1,2,2]
進階:
 
一個直觀的解決方案是使用計數排序的兩趟掃描算法。
首先,迭代計算出0、1 和 2 元素的個數,然后按照0、1、2的排序,重寫當前數組。
你能想出一個僅使用常數空間的一趟掃描算法嗎?
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/sort-colors
 
關鍵點和易錯點:
(1)經典的三色國旗問題,想到了解法,但自己把實現弄復雜了,看實現1代碼,不忍直視,實現2才是不容易錯且好理解的
(2)一開始沒把跟0交換和跟2交換,當前指針是否要移動想清楚,導致程序寫的復雜了點
 
實現1:
class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """

        n = len(nums)
        red_point = 0
        white_point = 0
        blue_point = n-1

        while white_point <= blue_point:
            if nums[white_point] == 0:
                while red_point < white_point:
                    if nums[red_point] == 0:
                        red_point += 1
                    else:
                        break
                if red_point < white_point:
                    nums[white_point] = nums[red_point]
                    nums[red_point] = 0
                    red_point += 1
                else:
                    white_point += 1
            elif nums[white_point] == 2:
                while blue_point > white_point:
                    if nums[blue_point] == 2:
                        blue_point -= 1
                    else:
                        break
                if blue_point > white_point:
                    nums[white_point] = nums[blue_point]
                    nums[blue_point] = 2
                    blue_point -= 1
                else:
                    white_point += 1
            else:
                white_point += 1

        return nums

 

實現2:

class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """

        red_point = cur_point = 0
        blue_point = len(nums)-1

        while cur_point <= blue_point:
            if nums[cur_point] == 0:
                nums[cur_point], nums[red_point] = nums[red_point], nums[cur_point]
                red_point += 1
                cur_point += 1
            elif nums[cur_point] == 2:
                nums[cur_point], nums[blue_point] = nums[blue_point], nums[cur_point]
                blue_point -= 1
            else:
                cur_point += 1

        return nums

 

5.最大數
給定一組非負整數,重新排列它們的順序使之組成一個最大的整數。
 
示例 1:
輸入: [10,2]
輸出: 210
示例 2:
 
輸入: [3,30,34,5,9]
輸出: 9534330
說明: 輸出結果可能非常大,所以你需要返回一個字符串而不是整數。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/largest-number
 
關鍵點和易錯點:
(1)案例[0, 0]這個真的是略坑
(2)對sort函數用法不是很了解,有x.sort和sorted這兩種用法,x.sort就是對x就行排序了,sorted則是返回一個排序后的數組,sort函數里的參數cmp=cmp_func,reverse=False,reverse是False是默認值,表示默認升序排序,為True則降序,在cmp_func里你不用管給出的兩個參數x,y,對應數組的哪個數,只要是x>y返回1,x<y返回-1,相等則返回0這種原則即可。還有默認是從小到大排序的,要注意。
 
提交代碼:
class Solution(object):
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        
        def custom_cmp(x, y):
            if x == y:
                return 0
            
            x += y
            y += x
            all_len = len(x)
            for cur in xrange(all_len):
                if x[cur] < y[cur]:
                    return -1
                elif x[cur] > y[cur]:
                    return 1
            return 0
            
        str_nums = [str(num) for num in nums]
            
        str_nums.sort(cmp=custom_cmp, reverse=True)
        if len(str_nums) == 0 or str_nums[0] == "0":
            return "0"
        return "".join(str_nums)

 

6.最大間距
給定一個無序的數組,找出數組在排序之后,相鄰元素之間最大的差值。
如果數組元素個數小於 2,則返回 0。
 
示例 1:
輸入: [3,6,9,1]
輸出: 3
解釋: 排序后的數組是 [1,3,6,9], 其中相鄰元素 (3,6) 和 (6,9) 之間都存在最大差值 3。
示例 2:
 
輸入: [10]
輸出: 0
解釋: 數組元素個數小於 2,因此返回 0。
說明:
 
你可以假設數組中所有元素都是非負整數,且數值在 32 位有符號整數范圍內。
請嘗試在線性時間復雜度和空間復雜度的條件下解決此問題。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/maximum-gap
 
關鍵點和易錯點:
(1)因為題目要求需要線性時間復雜度,所以常規下的排序肯定都是不符合的了,所以我們要向下O(n)復雜度的排序,剛好基數排序就能做到這點,基數排序的思想就是從數字的個數、十位、百位一直比下去,直到排好序,時間復雜度是O(n)
(2)除了基數排序,還有種方法也可以線性時間解決該問題,使用桶的方式(也可以說是鴿籠原理,即4只鴿子放3個籠子一定會有大於等於2個鴿子需要放在一個籠子里),它基於的原理就是用最大數減去最小數除以n-1,得到桶的大小,這個大小也表明了最大差距的數不會在同一個桶內,為啥這樣說呢,我們可以舉個例子,假設我的數字是1,2,3,4,5,6,那么桶大小=(max_num-min_num)/(數組長度-1)=(6-1)/(6-1)=1,也就是間隔至少也是1了,假設數字是1,2,3,4,6,則桶大小=(6-1)/(5-1)=1.25,即數字之間的間隔一定大於1.25,因為是整數,所以可以向上取整,數的最大間隔一定大於等於2,所以桶內的2個數一定不可能構成最大間隔,所以只需要比較桶與桶之間即可,n-1可以理解為n個數之間的空隙段。這個想清楚了代碼就容易理解了。
(3)上面的桶排序還要注意保證桶的大小要大於等於1
 
提交代碼(基數排序):
class Solution(object):
    def maximumGap(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        n = len(nums)
        if n <= 1:
            return 0
        
        max_num = max(nums)
        min_num = min(nums)
        
        bucket_size = int(math.ceil(float(max_num - min_num) / (n-1)))
        bucket_size = bucket_size if bucket_size > 0 else 1
        bucket_num = (max_num - min_num) / bucket_size + 1
        buckets = [None]*bucket_num
        
        for num in nums:
            bucket_index = (num-min_num)/bucket_size
            if buckets[bucket_index] is None:
                buckets[bucket_index] = {'max_num':num, 'min_num':num}
            else:
                buckets[bucket_index]['max_num'] = max(buckets[bucket_index]['max_num'], num)
                buckets[bucket_index]['min_num'] = min(buckets[bucket_index]['min_num'], num)
            
        max_interval = 0
        pre_bucket_max = buckets[0]['max_num']
        for i in xrange(1, bucket_num):
            if buckets[i] is not None:
                max_interval = max(max_interval, buckets[i]['min_num']-pre_bucket_max)
                pre_bucket_max = buckets[i]['max_num']
        
        return max_interval

 

提交代碼(桶排序):

class Solution(object):
    def maximumGap(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        n = len(nums)
        if n <= 1:
            return 0
        
        max_num = max(nums)
        min_num = min(nums)
        
        bucket_size = int(math.ceil(float(max_num - min_num) / (n-1)))
        bucket_size = bucket_size if bucket_size > 0 else 1
        bucket_num = (max_num - min_num) / bucket_size + 1
        buckets = [None]*bucket_num
        
        for num in nums:
            bucket_index = (num-min_num)/bucket_size
            if buckets[bucket_index] is None:
                buckets[bucket_index] = {'max_num':num, 'min_num':num}
            else:
                buckets[bucket_index]['max_num'] = max(buckets[bucket_index]['max_num'], num)
                buckets[bucket_index]['min_num'] = min(buckets[bucket_index]['min_num'], num)
            
        max_interval = 0
        pre_bucket_max = buckets[0]['max_num']
        for i in xrange(1, bucket_num):
            if buckets[i] is not None:
                max_interval = max(max_interval, buckets[i]['min_num']-pre_bucket_max)
                pre_bucket_max = buckets[i]['max_num']
        
        return max_interval

 

7.H指數
給定一位研究者論文被引用次數的數組(被引用次數是非負整數)。編寫一個方法,計算出研究者的 h 指數。
h 指數的定義: “h 代表“高引用次數”(high citations),一名科研人員的 h 指數是指他(她)的 (N 篇論文中)至多有 h 篇論文分別被引用了至少 h 次。(其余的 N - h 篇論文每篇被引用次數不多於 h 次。)”
 
示例:
輸入: citations = [3,0,6,1,5]
輸出: 3
解釋: 給定數組表示研究者總共有 5 篇論文,每篇論文相應的被引用了 3, 0, 6, 1, 5 次。
由於研究者有 3 篇論文每篇至少被引用了 3 次,其余兩篇論文每篇被引用不多於 3 次,所以她的 h 指數是 3。
 
說明: 如果 h 有多種可能的值,h 指數是其中最大的那個。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/h-index
 
關鍵點和易錯點:
(1)題目看起來非常的繞,但看懂后其實就是求最大的一個數x,滿足有x篇論文被引用次數大於等於x
(2)我想到的方式是用二分法來做,前提是數組要先排序好,用下標計算大於某個數的論文數量
(3)使用比較排序都不低於O(nlgn),所以要想突破O(n),只能考慮基數排序,用基數排序最關鍵的就是想到論文引用數量如果大於n的,可以當n算
 
提交代碼(二分法):
class Solution(object):
    def hIndex(self, citations):
        """
        :type citations: List[int]
        :rtype: int
        """
        
        citations.sort()
        max_h = 0
        n = len(citations)
        
        start = 0
        end = n-1
        while start <= end:
            mid = (start + end) / 2
            tmp_n = n - mid
            tmp_value = citations[mid]
            if tmp_value < tmp_n:
                start = mid + 1
            else:
                max_h = max(max_h, tmp_n)
                end = mid - 1
                
        return max_h

 

提交代碼(基數排序):

class Solution(object):
    def hIndex(self, citations):
        """
        :type citations: List[int]
        :rtype: int
        """
        
        n = len(citations)
        count = [0]*(n+1)
        for num in citations:
            count[min(num, n)] += 1
            
        tmp_n = 0
        for k in xrange(n, -1, -1):
            tmp_n += count[k]
            if k <= tmp_n:
                break
        return k

 

8.計算右側小於當前元素的個數
給定一個整數數組 nums,按要求返回一個新數組 counts。數組 counts 有該性質: counts[i] 的值是 nums[i] 右側小於 nums[i] 的元素的數量。
 
示例:
輸入: [5,2,6,1]
輸出: [2,1,1,0]
解釋:
5 的右側有 2 個更小的元素 (2 和 1).
2 的右側僅有 1 個更小的元素 (1).
6 的右側有 1 個更小的元素 (1).
1 的右側有 0 個更小的元素.
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
 
關鍵點和易錯點:
(1)這題一看就是逆序對的問題,立刻想到歸並排序,但不同的是逆序對只需要求出總的逆序對數,而這個需要求出每個數的逆序對數量,所以會多出個索引數組的問題
(2)我做這道題時,有維護索引數組,同時還把nums原數組也調換排序了,但其實可以不用對原數組進行排序,只需用一個下標數組進行排序,然后再開個數組來保存這個下標數在原始組中的位置即可
(3)有個固定思維需要改下,比如兩個數組2,3,5和1,8,9,i指向2,j指向1,這時發現1小於2,那么3和5肯定都大於1,則直接逆序對加3,這個3是包含了2,3,5每個的其中一個逆序對的,如果用for循環累加肯定超時,可以這樣算,當發現1小於2,那么也就是2大於1,即2的逆序對可以累加j和j以前的,這樣就不用for循環了
(4)這題還可以用線段樹實現,可以以后補充進來
 
提交代碼(我一開始的索引數組法,有點冗余,改變了原數組):
class Solution(object):
    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        
        n = len(nums)
        tmp_nums = [0]*n
        index_map = range(n)
        tmp_index = [0]*n
        results = [0]*n
        self.divide_combine_alg(nums, tmp_nums, index_map, tmp_index, results, 0, n-1)
        return results
        
    def divide_combine_alg(self, nums, tmp_nums, index_map, tmp_index, results, start, end):
        if end > start:
            mid =(start + end)/2
            self.divide_combine_alg(nums, tmp_nums, index_map, tmp_index, results, start, mid)
            self.divide_combine_alg(nums, tmp_nums, index_map, tmp_index, results, mid+1, end)
            i = start
            j = mid+1
            count = start
            while i <= mid and j <= end:
                if nums[i] <= nums[j]:
                    tmp_nums[count] = nums[i]
                    tmp_index[count] = index_map[i]
                    results[index_map[i]] += (j-mid-1)
                    i += 1
                else:
                    tmp_nums[count] = nums[j]
                    tmp_index[count] = index_map[j]
                    j += 1
                count += 1
            while i <= mid:
                tmp_nums[count] = nums[i]
                tmp_index[count] = index_map[i]
                count += 1
                results[index_map[i]] += (end-mid)
                i += 1
            while j <= end:
                tmp_nums[count] = nums[j]
                tmp_index[count] = index_map[j]
                count += 1
                j += 1

            for i in xrange(start, end+1):
                nums[i] = tmp_nums[i]
                index_map[i] = tmp_index[i]

 

提交代碼(看了別人的索引數組寫的,沒改變原數組,對下標排序):

class Solution(object):
    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        
        n = len(nums)
        indexes = range(n)
        tmp_index = [0]*n
        results = [0]*n
        self.divide_combine_alg(nums, indexes, tmp_index, results, 0, n-1)
        return results
        
    def divide_combine_alg(self, nums, indexes, tmp_index, results, start, end):
        if end > start:
            mid =(start + end)/2
            self.divide_combine_alg(nums, indexes, tmp_index, results, start, mid)
            self.divide_combine_alg(nums, indexes, tmp_index, results, mid+1, end)
            l = start
            r = mid+1
            for count in xrange(start, end+1):
                if l > mid:
                    tmp_index[count] = indexes[r]
                    r += 1
                elif r > end:
                    tmp_index[count] = indexes[l]
                    results[indexes[l]] += (end-mid)
                    l += 1
                elif nums[indexes[l]] <= nums[indexes[r]]:
                    tmp_index[count] = indexes[l]
                    results[indexes[l]] += (r-mid-1)
                    l += 1
                else:
                    tmp_index[count] = indexes[r]
                    r += 1

            for i in xrange(start, end+1):
                indexes[i] = tmp_index[i]

 

9.最接近原點的K個點
我們有一個由平面上的點組成的列表 points。需要從中找出 K 個距離原點 (0, 0) 最近的點。
(這里,平面上兩點之間的距離是歐幾里德距離。)
你可以按任何順序返回答案。除了點坐標的順序之外,答案確保是唯一的。
 
示例 1:
 
輸入:points = [[1,3],[-2,2]], K = 1
輸出:[[-2,2]]
解釋:
(1, 3) 和原點之間的距離為 sqrt(10),
(-2, 2) 和原點之間的距離為 sqrt(8),
由於 sqrt(8) < sqrt(10),(-2, 2) 離原點更近。
我們只需要距離原點最近的 K = 1 個點,所以答案就是 [[-2,2]]。
示例 2:
 
輸入:points = [[3,3],[5,-1],[-2,4]], K = 2
輸出:[[3,3],[-2,4]]
(答案 [[-2,4],[3,3]] 也會被接受。)
 
提示:
 
1 <= K <= points.length <= 10000
-10000 < points[i][0] < 10000
-10000 < points[i][1] < 10000
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/k-closest-points-to-origin
 
關鍵點和易錯點:
(1)因為之前做過類似的題目,所以一下就想到快速排序+分治的思想來解決該問題了
(2)因為快排的思想是划分為兩半,一半大於某個數,一半小於某個數,所以要求K個數只需找到那個分界點剛好是K-1即可,那么K-1左邊的數自然是最小的K個數
 
提交代碼:
class Solution(object):
    def kClosest(self, points, K):
        """
        :type points: List[List[int]]
        :type K: int
        :rtype: List[List[int]]
        """

        n = len(points)
        start = 0
        end = n - 1

        while True:
            org_start = start
            org_end = end
            tmp_list = [points[start][0], points[start][1]]
            tmp = points[start][0] * points[start][0] + points[start][1] * points[start][1]
            while start < end:
                while start < end and points[end][0] * points[end][0] + points[end][1] * points[end][1] >= tmp:
                    end -= 1
                if start < end:
                    points[start][0], points[start][1] = points[end][0], points[end][1]
                    start += 1
                while start < end and points[start][0] * points[start][0] + points[start][1] * points[start][1] <= tmp:
                    start += 1
                if start < end:
                    points[end][0], points[end][1] = points[start][0], points[start][1]
                    end -= 1
            points[start][0], points[start][1] = tmp_list[0], tmp_list[1]
            if start == K-1:
                return points[:K]
            elif start < K-1:
                start += 1
                end = org_end
            else:
                end = end - 1
                start = org_start

 

10.重構字符串
給定一個字符串S,檢查是否能重新排布其中的字母,使得兩相鄰的字符不同。
 
若可行,輸出任意可行的結果。若不可行,返回空字符串。
 
示例 1:
 
輸入: S = "aab"
輸出: "aba"
 
來源:力扣(LeetCode)
 
注意:
S 只包含小寫字母並且長度在[1, 500]區間內。
 
關鍵點和易錯點:
(1)一開始的思路是想到計數排序,然后用最多的開始排,但是這種是在某些情況下不對的
(2)可行方案是用計數排序統計,然后用插空的方式來排開,這里有幾個點要先想清楚,只要某個字母的重復數量不大於(n+1)/2,則一定是可以排出來的;程序里用了奇數和偶數的下標來間隔,在判斷條件里有個判斷是當字符的重復數量小於n/2+1則可放在奇數位置,因為根據插空法,偶數的位置肯定是大於等於奇數的位置,所以多的應該留給偶數那里放,舉個例子3,3,3,2,2。如果3,3,3都插入到奇數位置那么必然是不夠放的。這里多畫圖多思索,想清楚就好理解了。
(3)還有一種方法是用優先級隊列,每次都pop出當前剩余字符數量最多的兩個出來放入列表即可,這時一種貪心策略。這里使用了python的heapq最小堆數據結構。
 
插空法:
class Solution(object):
    def reorganizeString(self, S):
        """
        :type S: str
        :rtype: str
        """

        n = len(S)
        buckets = [0]*26
        max_rep = 0

        for c in S:
            offset = ord(c)-ord('a')
            buckets[offset] += 1
            if buckets[offset] > max_rep:
                max_rep = buckets[offset]

        if max_rep > (n+1)/2:
            return ""

        result = [None]*n
        even = 0
        odd = 1
        for i in xrange(26):
            if buckets[i] < n/2+1:
                while buckets[i] > 0 and odd < n:
                    result[odd] = chr(ord('a')+i)
                    buckets[i] -= 1
                    odd += 2
            while buckets[i] > 0:
                result[even] = chr(ord('a')+i)
                buckets[i] -= 1
                even += 2
        
        return "".join(result)

 

優先隊列法:

class Solution(object):
    def reorganizeString(self, S):
        """
        :type S: str
        :rtype: str
        """

        n = len(S)
        pq = [[0, chr(ord('a')+i)] for i in xrange(26)]
        max_rep = 0

        for c in S:
            offset = ord(c)-ord('a')
            pq[offset][0] -= 1
            if pq[offset][0] < max_rep:
                max_rep = pq[offset][0]

        if abs(max_rep) > (n+1)/2:
            return ""

        heapq.heapify(pq)

        result = []
        while len(pq) >= 2:
            count1, c1 = heapq.heappop(pq)
            count2, c2 = heapq.heappop(pq)
            if count1 == 0 or count2 == 0:
                heapq.heappush(pq, [count1, c1])
                heapq.heappush(pq, [count2, c2])
                break
            result.extend([c1, c2])
            count1 += 1
            count2 += 1
            if count1 != 0:
                heapq.heappush(pq, [count1, c1])
            if count2 != 0:
                heapq.heappush(pq, [count2, c2])

        if len(pq):
            count, c = heapq.heappop(pq)
            if count != 0:
                result.append(c)
        return "".join(result)

 

11.通過刪除字母匹配到字典里的最長單詞
給定一個字符串和一個字符串字典,找到字典里面最長的字符串,該字符串可以通過刪除給定字符串的某些字符來得到。如果答案不止一個,返回長度最長且字典順序最小的字符串。如果答案不存在,則返回空字符串。
 
示例 1:
 
輸入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
 
輸出:
"apple"
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting
 
關鍵點和易錯點:
(1)這道題目一拿到感覺好復雜啊,因為自己總是覺得應該想到個絕佳解法,負責度很低的解法,但其實標准答案的方法已經有想過了,只是覺得好像沒那么簡單
(2)暴力法我反而沒想到,官方暴力解法是直接枚舉字符串S的所有子序列,然后匹配d上的字符串
(3)根據題目我們可知可以對d的字符串進行排序,然后一個個跟S對比,看是否是它的子序列,如果是則直接輸出得到答案
 
提交代碼:
class Solution(object):
    def findLongestWord(self, s, d):
        """
        :type s: str
        :type d: List[str]
        :rtype: str
        """

        def isSubsequence(s, item):
            s_len = len(s)
            item_len = len(item)

            i = j = 0
            while i < s_len and j < item_len and (s_len-i) >= (item_len - j):
                if s[i] == item[j]:
                    j += 1
                i += 1
            return j == item_len

        def compare_func(x, y):
            if len(x) > len(y) or (len(x) == len(y) and x <= y):
                return 1
            return -1

        d.sort(cmp=compare_func, reverse=True)
        for item in d:
            if isSubsequence(s, item):
                return item
        return ""

 

12.車隊
N 輛車沿着一條車道駛向位於 target 英里之外的共同目的地。每輛車 i 以恆定的速度 speed[i] (英里/小時),從初始位置 position[i] (英里) 沿車道駛向目的地。一輛車永遠不會超過前面的另一輛車,但它可以追上去,並與前車以相同的速度緊接着行駛。此時,我們會忽略這兩輛車之間的距離,也就是說,它們被假定處於相同的位置。車隊 是一些由行駛在相同位置、具有相同速度的車組成的非空集合。注意,一輛車也可以是一個車隊。即便一輛車在目的地才趕上了一個車隊,它們仍然會被視作是同一個車隊。會有多少車隊到達目的地?
 
示例:
輸入:target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]
輸出:3
解釋:
從 10 和 8 開始的車會組成一個車隊,它們在 12 處相遇。
從 0 處開始的車無法追上其它車,所以它自己就是一個車隊。
從 5 和 3 開始的車會組成一個車隊,它們在 6 處相遇。
請注意,在到達目的地之前沒有其它車會遇到這些車隊,所以答案是 3。
 
提示:
 
0 <= N <= 10 ^ 4
0 < target <= 10 ^ 6
0 < speed[i] <= 10 ^ 6
0 <= position[i] < target
所有車的初始位置各不相同。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/car-fleet
 
關鍵點和易錯點:
(1)思路非常簡單,也很容易想到,但很多細節容易錯誤,且有挺多坑的,比如按初始position排序后,如果按照判斷第1輛能否趕上第2輛的思想,就錯了,因為第一輛車可能趕上了第三輛車。比如測試用例10 [0,4,2] [2,1,3],因為(0,2)和(2,3)都可以趕上(4,1),所以結果是1
(2)正確的思路是從后往前,如果后面一輛趕上前面一輛就覆蓋掉這這輛的位置和速度,相當於取小的,否則加1個車隊
 
提交代碼:
class Solution(object):
    def carFleet(self, target, position, speed):
        """
        :type target: int
        :type position: List[int]
        :type speed: List[int]
        :rtype: int
        """

        n = len(position)
        if n == 0:
            return 0

        car_arr = [[position[i], speed[i]] for i in xrange(n)]
        car_arr.sort(key=lambda x: x[0])

        car_group_num = 1 if n > 0 else 0
        for i in xrange(n-2, -1, -1):
            if float(target-car_arr[i][0])/car_arr[i][1] <= float(target-car_arr[i+1][0])/car_arr[i+1][1]:
                car_arr[i][0], car_arr[i][1] = car_arr[i+1][0], car_arr[i+1][1]
            else:
                car_group_num += 1

        return car_group_num

 

13.煎餅排序
給定數組 A,我們可以對其進行煎餅翻轉:我們選擇一些正整數 k <= A.length,然后反轉 A 的前 k 個元素的順序。我們要執行零次或多次煎餅翻轉(按順序一次接一次地進行)以完成對數組 A 的排序。
 
返回能使 A 排序的煎餅翻轉操作所對應的 k 值序列。任何將數組排序且翻轉次數在 10 * A.length 范圍內的有效答案都將被判斷為正確。
 
示例 1:
 
輸入:[3,2,4,1]
輸出:[4,2,4,3]
解釋:
我們執行 4 次煎餅翻轉,k 值分別為 4,2,4,和 3。
初始狀態 A = [3, 2, 4, 1]
第一次翻轉后 (k=4): A = [1, 4, 2, 3]
第二次翻轉后 (k=2): A = [4, 1, 2, 3]
第三次翻轉后 (k=4): A = [3, 2, 1, 4]
第四次翻轉后 (k=3): A = [1, 2, 3, 4],此時已完成排序。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/pancake-sorting
 
關鍵點和易錯點:
(1)這道題目的思路很簡單,很快就能想到,先把最大的通過翻轉移到第一位,然后再翻轉移到最后一位,重復這兩個步驟直到全部排好。難在如何不模擬翻轉可以實現
(2)我一開始寫的代碼時模擬了整個翻轉的過程,所以效率很低,因為做了很多翻轉操作
(3)官方題解是沒有翻轉的,說實話寫的有點難看懂,其實不用翻轉的秘訣在於一個數假設位置為i,則它經過一次翻轉,假設翻轉系數是k,則經過翻轉i的位置會變為k-i+1。舉例,3,2,4,1,如果要將4弄到最后則可以先以k=3翻轉,然后以k=4翻轉就可以將4翻轉到最后一位,看2的位置,它一開始的位置是2的,故i=2,經過k=3翻轉位置變為i=k-i+1=3-2+1=2,經過k=4翻轉位置變為i=k-i+1=4-2+1=3。不信你可以手動翻下,2的位置都是如計算所得。所以秘訣就在這里,每次不是去模擬翻轉,而是通過記錄翻轉的次數計算得到經過翻轉次數后這時的位置在哪來去進行翻轉,所以代碼里的第二個for循環其實就是在計算此時它經過多次翻轉后的位置,因為知道了位置在哪,直接執行i翻轉和n翻轉即可。
 
模擬翻轉提交代碼:
class Solution(object):
    def pancakeSort(self, A):
        """
        :type A: List[int]
        :rtype: List[int]
        """

        n = len(A)
        index = sorted(range(n), key=lambda i: A[i])

        def swap_value(A, index, end):
            start = 0
            while start < end:
                index[A[start]-1], index[A[end]-1] = index[A[end]-1], index[A[start]-1]
                A[start], A[end] = A[end], A[start]
                start += 1
                end -= 1

        swap_flag = n-1
        convert = []
        while swap_flag > 0:
            num_index = index[swap_flag]
            if num_index != swap_flag:
                if num_index != 0:
                    convert.append(num_index+1)
                    swap_value(A, index, num_index)
                convert.append(swap_flag+1)
                swap_value(A, index, swap_flag)
            swap_flag -= 1
        return convert

 

直接計算位置的提交代碼:

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

        n = len(A)
        index_pos = sorted(range(1, n+1), key=lambda i: -A[i-1])

        convert = []
        for i in index_pos[:-1]:
            for c in convert:
                if c >= i:
                    i = c - i + 1
            if i < n:
                convert.extend([i, n])
            n -= 1
        return convert

 

14.將矩陣按對角線排序
給你一個 m * n 的整數矩陣 mat ,請你將同一條對角線上的元素(從左上到右下)按升序排序后,返回排好序的矩陣。
 
示例 1:
 
輸入:mat = [[3,3,1,1],[2,2,1,2],[1,1,1,2]]
輸出:[[1,1,1,1],[1,2,2,2],[1,2,3,3]]
 
提示:
 
m == mat.length
n == mat[i].length
1 <= m, n <= 100
1 <= mat[i][j] <= 100
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/sort-the-matrix-diagonally
 
關鍵點和易錯點:
(1)要分兩部分處理對角線,對角線排好序后直接填充回去即可
(2)填充回去一開始沒想到
 
提交代碼:
class Solution(object):
    def diagonalSort(self, mat):
        """
        :type mat: List[List[int]]
        :rtype: List[List[int]]
        """

        row = len(mat)
        if row == 0:
            return mat
        col = len(mat[0])

        diags = []
        for c in xrange(col-1, -1, -1):
            tmp_row = 0
            tmp_col = c
            tmp = []
            while tmp_row < row and tmp_col < col:
                tmp.append(mat[tmp_row][tmp_col])
                tmp_row += 1
                tmp_col += 1
            tmp.sort()

            tmp_row = 0
            tmp_col = c
            i = 0
            while tmp_row < row and tmp_col < col:
                mat[tmp_row][tmp_col] = tmp[i]
                i += 1
                tmp_row += 1
                tmp_col += 1

        for c in xrange(1, row):
            tmp_row = c
            tmp_col = 0
            tmp = []
            while tmp_row < row and tmp_col < col:
                tmp.append(mat[tmp_row][tmp_col])
                tmp_row += 1
                tmp_col += 1
            tmp.sort()
            
            tmp_row = c
            tmp_col = 0
            i = 0
            while tmp_row < row and tmp_col < col:
                mat[tmp_row][tmp_col] = tmp[i]
                i += 1
                tmp_row += 1
                tmp_col += 1
        
        return mat

 

15.區間和的個數
給定一個整數數組 nums,返回區間和在 [lower, upper] 之間的個數,包含 lower 和 upper。
區間和 S(i, j) 表示在 nums 中,位置從 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
 
說明:
最直觀的算法復雜度是 O(n2) ,請在此基礎上優化你的算法。
 
示例:
 
輸入: nums = [-2,5,-1], lower = -2, upper = 2,
輸出: 3
解釋: 3個區間分別是: [0,0], [2,2], [0,2],它們表示的和分別為: -2, -1, 2。
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/count-of-range-sum
 
關鍵點和易錯點:
(1)其實最關鍵的點是要理解前綴和,而且其實求解只要理解這條算式即可,lower <= sj - si <= upper(j>=i),只要符合這條算式,那么[i,j]就是一個符合的求解
(2)可以利用歸並的特性。假設下列是一個數組的前綴和:
藍色部分是歸並的前半部分,黃色是歸並的右半部分,j用來指向黃色部分的下標,i用來指向藍色部分的下標,問題變成在兩部分各有序的情況下怎么求解符合lower <= sj - si <= upper(j>=i)的i,j的個數對,算法步驟:
a.用left變量(這里的left其實就是上面算式的i)指向藍色部分第一個下標位置,low和up變量(這兩個變量對應上面算式的j,但一個是低界限位置,一個是高界限位置)指向黃色的第一個下標
b.當S[low] - S[left] < lower,則low+1,因為left向右只會更大,所以-2這個值是不可能了,直到條件不符合為止
c.當S[up] - S[left] < upper,則說明這個高界限還可以向前,即up+1,直到條件不符合位置
d.用高界限-低界限的數量值就是當前滿足left這個數的情況下,右邊部分滿足的數量個數,即i下標定了,有多少個j是滿足的
易錯點:注意S[low] - S[left] > upper的情況,這種情況應該讓left+1了
這種方法其實跟逆序對的思想有點像,其實關鍵還是要把lower <= sj - si <= upper(j>=i)這條公式理解到,而且多想想歸並的這種技巧是否有助於解題,逆向思維推理下
 
(3)還可以使用二分法,只要把算式變換下,lower <= sj - si <= upper(j>=i)變為lower + si <= sj <= upper + si(j>=i),也就是說我們可以倒序(因為現在是sj來判斷,j是在i后面的)來判斷,相當於一段有序的數組,我只要找到一段范圍的數的個數是多少,相當於一段數字,我把一個框框放進去,能套住多少個數字
易錯點:別忘了,最前面那個0要加上,因為它是用來判斷全部排好序后,屬於范圍內的有多少個,舉例上面這個數組,是-2,當-2也插入到排好序的數組時,需要再判斷一輪,而不是就終止了。
bisect模塊介紹:
bisect是用來針對有序數組的插入和排序操作的一個模塊。
bisect_left(a, x, lo=0, hi=None):查找該數值將會插入的位置並返回,而不會插入。如果x存在在a中則返回x左邊的位置
bisect_right(a, x, lo=0, hi=None):查找該數值將會插入的位置並返回,而不會插入。如果x存在在a中則返回x右邊的位置
insort:在列表a中插入元素x,並在排序后保持排序。如果x已經在a中,把它插入右x的右邊。
 
提交代碼:
歸並法:
class Solution(object):
    def countRangeSum(self, nums, lower, upper):
        """
        :type nums: List[int]
        :type lower: int
        :type upper: int
        :rtype: int
        """

        n = len(nums)
        if n  == 0:
            return 0
        S = [0]*n
        S[0] = nums[0]
        for i in xrange(1, n):
            S[i] = S[i-1] + nums[i]
        return self.mergeSort(S, 0, n-1, lower, upper, 0)

    def mergeSort(self, S, start, end, lower, upper, count):
        if start == end:
            count = count+1 if S[start] >= lower and S[end] <= upper else count
            return count
        mid = (start + end) / 2
        count = self.mergeSort(S, start, mid, lower, upper, count)
        count = self.mergeSort(S, mid+1, end, lower, upper, count)
        left = start
        low = up = mid + 1
        
        # cal count
        while left <= mid and (low <= end or up <= end):
            while low <= end and S[low] - S[left] < lower:
                low += 1
            up = low if low > up else up
            if low <= end and S[low]-S[left] <= upper:
                while up <= end and S[up] - S[left] <= upper:
                    up += 1
                count += (up - low)
            left += 1

        # sort list
        tmp = []
        i = start
        j = mid + 1
        while i <= mid or j <= end:
            if i > mid:
                tmp.extend(S[j:end+1])
                break
            elif j > end:
                tmp.extend(S[i:mid+1])
                break
            else:
                if S[i] <= S[j]:
                    tmp.append(S[i])
                    i += 1
                else:
                    tmp.append(S[j])
                    j += 1
        for i in xrange(len(tmp)):
            S[start+i] = tmp[i]
        return count

 

二分法:

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

        n = len(nums)
        if n  == 0:
            return 0
        S = [0]*(n+1)
        for i in xrange(1, n+1):
            S[i] = S[i-1] + nums[i-1]
        
        sort_list = []
        count = 0
        for tmp_sum in S[::-1]:
            lower_s = lower + tmp_sum
            up_s = upper + tmp_sum
            l = bisect.bisect_left(sort_list, lower_s)
            r = bisect.bisect_right(sort_list, up_s)
            count += (r-l)
            bisect.insort(sort_list, tmp_sum)
        return count

 

16.擺動排序2
給定一個無序的數組 nums,將它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的順序。
 
示例 1:
 
輸入: nums = [1, 5, 1, 1, 6, 4]
輸出: 一個可能的答案是 [1, 4, 1, 5, 1, 6]
 
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/wiggle-sort-ii
 
關鍵點和易錯點:
(1)相當二分法找到中間數不難,三分法算法也不難,難的是怎么處理中間的重復數字保證其不會並在一起,比如經過三分法算法后變成1,1,2,2,2,3,我們會分成兩部分,1,1,2和2,2,3,然后分別放到偶數位置和奇數位置上,但這樣會變成1,2,1,2,2,3,這時有2並在一起了,解決方法是兩部分做逆序,變成2,1,1和3,2,2,再排后就變為2,3,1,2,1,2,這樣就保證不會並在一起了,但空間復雜度會是O(n),要想使得空間復雜度為O(1),需要特定的方式交換數字,讓right_part都在奇數上,left_part都是偶數上,看到有別人的思路是虛擬地址映射的方式,其實是下標映射,不過沒看懂其交換思想,也很難想到,后續可以繼續研究下,鏈接地址:
 
提交代碼:
class Solution(object):
    def wiggleSort(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """

        n = len(nums)
        if n <= 1:
            return nums

        mid_index = (n-1)/2

        start = 0
        end = n-1
        while True:
            tmp = nums[start]
            org_start = start
            org_end = end
            while start < end:
                while start < end and nums[end] >= tmp:
                    end -= 1
                if start < end:
                    nums[start] = nums[end]
                    start += 1
                while start < end and nums[start] <= tmp:
                    start += 1
                if start < end:
                    nums[end] = nums[start]
                    end -= 1
            nums[start] = tmp
            if start == mid_index:
                break
            elif start < mid_index:
                start = start + 1
                end = org_end
            else:
                end = start - 1
                start = org_start
        
        # 3-way-partation
        mid_value = nums[mid_index]
        i = j = 0
        k = n-1
        while j < k:
            if nums[j] > mid_value:
                nums[j], nums[k] = nums[k], nums[j]
                k -= 1
            elif nums[j] < mid_value:
                nums[i], nums[j] = nums[j], nums[i]
                i += 1
                j += 1
            else:
                j += 1
        
        left_part = nums[:mid_index+1]
        right_part = nums[mid_index+1:]
        for i in xrange(len(left_part)):
            nums[2*i] = left_part[-i-1]
        for i in xrange(len(right_part)):
            nums[1+2*i] = right_part[-i-1]
        return nums

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM