leetcode75_颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

这道题是三路快排的变种:1作为枢轴值,我们要得到两个分界点: 0区域的右边界zero,2区域的左边界two

遍历nums,当前cursor为i:
当nums[i]的值为枢轴值1时,i++;
当nums[i]的值为2时,two的值先减1,而后交换nums[i]与nums[two],此时再观察nums[i]的值;
当nums[i]的值为0时,zero++,而后交换nums[i]与nums[zero],i++;
当 i = two时,结束循环

最好情况下,枢轴值刚好是中值,因此二分树很均衡,深度为log(n),每次二分点的寻找需要遍历整个数组,对于每一层来说,就是遍历n个元素因此复杂度O(nlog(n))
最坏情况下, 每次待排数组都只比上一次少一个元素,为斜树,一共要n-1次递归,对于第i次排序,要遍历n-i个元素,因此复杂度1+2+…+n-1 = n(n-1)/2,为O(n2)

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        # 通常三路快排right_bound_of_range_less_than_pivot初始为0,它对应的值为枢轴值
        # 这样while循环结束后,0位置上的值依然是保持不动的,
        # 需要把它交换到当前right_bound_of_range_less_than_pivot上面
        # 我们这道题是已知枢轴值为1,而不是用初始0位置上的值作为枢轴值
        right_bound_of_range_less_than_pivot = -1
        left_bound_of_range_larger_than_pivot = len(nums)
        # 遍历的右边界是动态变化的,因此用while
        current_index = right_bound_of_range_less_than_pivot + 1
        while current_index < left_bound_of_range_larger_than_pivot:
            if nums[current_index] == 0:
                right_bound_of_range_less_than_pivot += 1
                self.swap(nums, current_index, right_bound_of_range_less_than_pivot)
                current_index += 1
            elif nums[current_index] == 2:
                left_bound_of_range_larger_than_pivot -= 1
                self.swap(nums, current_index, left_bound_of_range_larger_than_pivot)            
            else:
                current_index += 1
    
    def swap(self, nums, i, j):
        nums[i], nums[j] = nums[j], nums[i]
        return nums

参考资料
快速排序算法

def quick_sort(nums):
    q_sort(nums, 0, len(nums)-1)


def q_sort(nums, l, r):
    if l >= r:
        return
    # 对于数组nums以l和r为边界的一段来说
    # 在中间选则一个枢轴点,使得它左边的数都比它小,它右边的数都比它大
    p_index = partition(nums, l, r)
    # 这样就可以对左边进行递归排序,使得枢轴点左边为有序的
    q_sort(nums, l, p_index-1)
    # 同样也可以对右边进行递归排序,使得枢轴点右边为有序的
    q_sort(nums, p_index+1, r)


def partition(nums, l, r):
    # 左边界对应的值定义为枢轴值
    pivot = nums[l]
    # 令枢轴点初始化为左边界
    pivot_index = l
    # 从左边界的右边一位开始遍历到右边界
    for i in range(l+1, r+1):
        # 如果对应的数比枢轴值小
        if nums[i] < pivot:
            # 就把枢轴点右移一位
            pivot_index += 1
            # 并且把那个小值放到当前枢轴点上, 把当前枢轴点上的值换过去
            # 每个当前枢轴点上的值都是小于枢轴值的
            swap(nums, i, pivot_index)
    # 最后将枢轴值放到当前枢轴点,当前枢轴点上的值换到左边界
    # 使得枢轴点上的值等于枢轴值,枢轴点左侧的数都比它小,右侧的数都比他大
    swap(nums, l, pivot_index)
    return pivot_index

def swap(nums, i, j):
    nums[i], nums[j] = nums[j], nums[i]
    return nums

三路快排算法

def quick_sort(nums):
    q_sort3ways(nums, 0, len(nums)-1)

def q_sort3ways(nums, l, r):
    if l >= r:
        return
    # 获得两个点,
    # 第一点及其左边全部<枢轴值
    # 第二个点及其右边全部>枢轴值
    right_bound_of_range_less_than_pivot, \
    left_bound_of_range_larger_than_pivot = partition3ways(nums, l, r)
    # 对左右两段递归调用本函数,使得左右两段分别有序
    q_sort3ways(nums, l, right_bound_of_range_less_than_pivot)
    q_sort3ways(nums, left_bound_of_range_larger_than_pivot, r)


def partition3ways(nums, l, r):
    # 左边界的值定义为枢轴值
    pivot = nums[l]
    # 左枢轴点初始化为左边界
    right_bound_of_range_less_than_pivot = l
    # 右枢轴点初始化为右边界的右边一位
    left_bound_of_range_larger_than_pivot = r + 1
    # 从左边界右边一位开始往右遍历
    i = l + 1
    while i < left_bound_of_range_larger_than_pivot:
        # 如果取值小于枢轴值就把左枢轴点右移一位,并且这个小值放到当前左枢轴点,
        # 将当前左枢轴点上的值(等于枢轴值)放到当前遍历点,
        # 因此当前左枢轴点的右边一位一直到当前遍历点上的所有值都是等于枢轴值的
        # 既然当前遍历点上的值已知,此时就可以继续看下一个点
        if nums[i] < pivot:
            right_bound_of_range_less_than_pivot += 1
            swap(nums, i, right_bound_of_range_less_than_pivot)
            i += 1
        # 如果取值大于枢轴值就把右枢轴点左移一位,并且这个大值就放到当前右枢轴点
        # 当前右枢轴点上的值放到当前遍历点上,但是这个值是未知大小的,因此不继续看下一个点而是判断这个点
        elif nums[i] > pivot:
            left_bound_of_range_larger_than_pivot -= 1
            swap(nums, i, left_bound_of_range_larger_than_pivot)
        # 如果取值等于枢轴值,则继续看下一个点
        else:
            i += 1
    # 此时左枢轴点上的值是小于枢轴值的,需要将当前左枢轴点上的值放到左边界,将左边界上的枢轴值放到当前左枢轴点,
    # 使得左枢轴点左边的值全部小于枢轴值
    swap(nums, right_bound_of_range_less_than_pivot, l)
    # 既然当前左枢轴点上的值等于枢轴值,它左边一位是小于枢轴值的,故返回这个位置
    # 当前右枢轴点及其右边的值是大于枢轴值的
    return right_bound_of_range_less_than_pivot-1, left_bound_of_range_larger_than_pivot

def swap(nums, i, j):
    nums[i], nums[j] = nums[j], nums[i]
    return nums
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述: 给定一个字符串,请将字符串里的字符按照出现的频率降序排列。 示例 1: 输入: "tree" 输出: "eert" 解释: 'e'出现两次,'r'和't'都只出现一次。因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。 示例 2: 输入: "cccaaa" 输出: "cccaaa" 解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。注意"cacaca"是不正确的,因为相同的字母必须放在一起。 示例 3: 输入: "Aabb" 输出: "bbAa" 解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。注意'A'和'a'被认为是两种不同的字符。 Java代码如下: ``` import java.util.*; public class Solution { public String frequencySort(String s) { if (s == null || s.length() == 0) { return ""; } Map<Character, Integer> map = new HashMap<>(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); map.put(c, map.getOrDefault(c, 0) + 1); } List<Map.Entry<Character, Integer>> list = new ArrayList<>(map.entrySet()); Collections.sort(list, (o1, o2) -> o2.getValue() - o1.getValue()); StringBuilder sb = new StringBuilder(); for (Map.Entry<Character, Integer> entry : list) { char c = entry.getKey(); int count = entry.getValue(); for (int i = 0; i < count; i++) { sb.append(c); } } return sb.toString(); } } ``` 解题思路: 首先遍历字符串,使用HashMap记录每个字符出现的次数。然后将HashMap转换为List,并按照出现次数从大到小进行排序。最后遍历排序后的List,将每个字符按照出现次数依次添加到StringBuilder中,并返回StringBuilder的字符串形式。 时间复杂度:O(nlogn),其中n为字符串s的长度。遍历字符串的时间复杂度为O(n),HashMap和List的操作时间复杂度均为O(n),排序时间复杂度为O(nlogn),StringBuilder操作时间复杂度为O(n)。因此总时间复杂度为O(nlogn)。 空间复杂度:O(n),其中n为字符串s的长度。HashMap和List的空间复杂度均为O(n),StringBuilder的空间复杂度也为O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值