LeetCode算法题-排序类

Posted luohaixian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

 

以上是关于LeetCode算法题-排序类的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# leetcode算法题:排序链表

⭐算法入门⭐《二分枚举》中等02 —— LeetCode 面试题 10.09. 排序矩阵查找

算法——查找排序相关面试题和leetcode使用

Leetcode练习(Python):数组类:第41题:给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

Leetcode练习(Python):数组类:第75题:给定一个包含红色白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色白色蓝色顺序排列。

LeetCode刷题笔记-数据结构-day21