排序与检索--Leetcode(python)

基于比较的内部排序算法复杂度

https://blog.csdn.net/weixin_41571493/article/details/81875088
排序与检索--Leetcode(python)_第1张图片
时间复杂度的下界是o(nlogn)证明:
对于n个待排序元素,在未比较时,可能的正确结果有n!种。在经过一次比较后,其中两个元素的顺序被确定,所以可能的正确结果剩余n!/2种。依次类推,直到经过m次比较,剩余可能性 n ! / ( 2 m ) n!/(2^m) n!/(2m)种。直到 n ! / ( 2 m ) < = 1 n!/(2^m)<=1 n!/(2m)<=1时,结果只剩余一种。此时的比较次数m为o(nlogn)次。所以基于排序的比较算法,最优情况下,复杂度是o(nlogn)的。

最大数

给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。

示例 1:

输入: [10,2]
输出: 210
示例 2:

输入: [3,30,34,5,9]
输出: 9534330
说明: 输出结果可能非常大,所以你需要返回一个字符串而不是整数。

解法
其实就是写一个排序方法,判断两个字符串的大小。两个字符串数字a,b,
若a+b 记一下map,sorted,lambda的用法。

class Solution(object):
    def largestNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: str
        """
        
        if not nums:
            return ""
        
        # nums = [str(x) for x in nums]
        nums = sorted(list(map(str, nums)), cmp = lambda x, y: cmp(x+y, y+x), reverse = True)
        if nums[0] == '0':
            return '0'
        else:
            return ''.join(nums)

摆动排序 II

给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。

示例 1:

输入: nums = [1, 5, 1, 1, 6, 4]
输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6]
示例 2:

输入: nums = [1, 3, 2, 2, 3, 1]
输出: 一个可能的答案是 [2, 3, 1, 3, 1, 2]
说明:
你可以假设所有输入都会得到有效的结果。

进阶:
你能用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现吗?

解法
想到对数组从小到大排序,然后从左到右间隔排序,就像示例1一样,奇数位置是前半排序,偶数位置是后半排序。但是由于排序会出现中间好几个数相同的情况,比如[4, 5, 5, 6],按照这个方法得到的结果还是[4, 5, 5, 6],所以可以在赋值的时候,倒着赋值,即按照从大到小的顺序。
看了网上的答案,发现思想相同,但是代码简洁的多,特别是求中间位置的时候直接用len(nums[::2]),以及赋值的时候不需要循环。

class Solution(object):
    def wiggleSort(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        tmp = sorted(nums, reverse=False)
        mid = len(tmp) / 2 if len(tmp) % 2 == 0 else len(tmp) / 2 + 1
        #print(mid, tmp)
        i = 0
        j = mid - 1
        k = len(nums) - 1
        while i < len(nums):
            nums[i] = tmp[j]
            i += 1
            if i < len(nums):
                nums[i] = tmp[k]
                i += 1
                j -= 1
                k -= 1
        
class Solution(object):
    def wiggleSort(self, nums):

        nums.sort()
        half = len(nums[::2])
        nums[::2], nums[1::2] = nums[:half][::-1], nums[half:][::-1]

寻找峰值

峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

示例 1:

输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:

输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
说明:

你的解法应该是 O(logN) 时间复杂度的。

解法
因为时间复杂度是O(logN)的,所以不能直接遍历,logN可以想到二分法。注意数组的0和len-1位置也是有可能的峰值。
此时我们应该考虑nums[k]和nums[k+1]的关系。如果我们nums[k]>nums[k+1],我们应该让j=k,因为k很可能就是峰值(因为右边小,而左边存在-inf)。而如果nums[k]

class Solution(object):
    def findPeakElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        l = 0
        r = len(nums) -1
        while l < r:
            mid = (l + r) // 2
            
            if nums[mid] <= nums[mid + 1]:
                l = mid + 1
            else:
                r = mid
                
        return l
        

寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

  1. 不能更改原数组(假设数组是只读的)。
  2. 只能使用额外的 O(1) 的空间。
  3. 时间复杂度小于 O(n2) 。
  4. 数组中只有一个重复的数字,但它可能不止重复出现一次。

解法
参考二分法解法
快慢指针的方法

二分法代码:

class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l, r = 0, len(nums) - 1
        
        while l < r:
            mid = (l + r) // 2
            
            cnt = 0
            for x in nums:
                if x <= mid:
                    cnt += 1
                    
            if cnt <= mid:
                l = mid + 1
            else:
                r = mid
                
        return l

快慢指针代码:

class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        slow = nums[0]
        fast = nums[nums[0]]
        while slow != fast:
            slow = nums[slow]
            fast = nums[nums[fast]]

        entry = 0
        while entry != slow:
            entry = nums[entry]
            slow = nums[slow]

        return entry

颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

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

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

解法
同时使用3个指针,这样只需要进行一次遍历,如果是0,则往前移动,如果是2则往后。
排序与检索--Leetcode(python)_第2张图片
注意,在循环过程中,如果遇到元素等于2的情况,由于数字从end位置调换过来,因此需要再对该位置的数字进行判断,因此这个时候的k不能也+1;而从前往后遍历的时候,当元素等于1的时候,前面的数字替换过来不会有错,因为前面的数不可能出现2。

class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        s, e = 0, len(nums) - 1
        k = 0
        while k <= e:
            if nums[k] == 0:
                nums[s], nums[k] = nums[k], nums[s]
                s += 1
                k += 1
            elif nums[k] == 2:
                nums[e], nums[k] = nums[k], nums[e]
                e -= 1
            else:
                k += 1
                
        return nums

合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6

解法

  1. 思路一:最简单的思路是把链表都加入一个数组中,然后排序,再加到最后的链表中,算法复度为O(nlog(n))
  2. 思路二:采用最小堆的思想,将K个链表的头节点放入优先队列中,遍历时返回队首元素,并把该队首元素的下一节点放入优先队列中,直至优先队列为空。

思路二的解法:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # @param a list of ListNode
    # @return a ListNode
    def mergeKLists(self, lists):
        heap = []
        for node in lists:
            if node != None: 
                heap.append((node.val, node))
        heapq.heapify(heap)
        head = ListNode(0)
        curr = head
        while heap:
            pop = heapq.heappop(heap)
            curr.next = ListNode(pop[0])
            curr = curr.next
            if pop[1].next: 
                heapq.heappush(heap, (pop[1].next.val, pop[1].next))
        return head.next

数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解法

  • 哈希法:可以先遍历一遍数组,在map中存每个元素出现的次数,然后再遍历一次数组,找出众数。
  • 排序法:可以先将数组排序,然后可能的众数肯定在数组中间,然后判断一下。
  • 投票法:加入数组中存在众数,那么众数一定大于数组的长度的一半。思想就是:如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。

注意:记得最后要判断一下最后得到的数字是否真的是众数。这几种方法的时间复杂度都是O(n)。

# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        cnt, tmp = 1, numbers[0]
        for x in numbers[1:]:
            if x == tmp:
                cnt += 1
            else:
                cnt -= 1
                if cnt == 0:
                    cnt = 1
                    tmp = x
                    
        cnt = 0
        for x in numbers:
            if x == tmp:
                cnt += 1
        if cnt > len(numbers)/2:
            return tmp
        
        return 0

把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解法

  • 冒泡排序:先将数组中每个元素转换成String类型,然后进行排序,如果str(a) + str(b) > str(b) + str(a),说明ab > ba,应该把b排在a前面。
  • 使用sorted函数和匿名函数
def PrintMinNumber(self, numbers):
    # write code here
    n = len(numbers)
    for i in range(n):
        for j in range(i+1, n):
            if int(str(numbers[i]) + str(numbers[j]) > str(numbers[j]) + str(numbers[i])):
                numbers[j], numbers[i] = numbers[i], numbers[j]
    return ''.join([str(i) for i in numbers])
# -*- coding:utf-8 -*-
class Solution:
    def PrintMinNumber(self, numbers):
        # write code here
        lmb = lambda a, b: int(str(a) + str(b)) - int(str(b) + str(a))
        numbers = sorted(numbers, cmp=lmb)
        res = "".join(str(x) for x in numbers)
        return res

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解法
参考https://www.nowcoder.com/questionTerminal/beb5aa231adc45b2a5dcc5b62c93f593?answerType=1&f=discussion

  • 辅助数组: 遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。
  • In-place: 初始化操作:记录一个变量i表示已经将奇数放好的下一个位置,显然最开始i=0,表示还没有一个奇数放好。j 表示数组的下标,初始值为0, 表示从下标0开始遍历。如果遇到偶数,j++;如果遇到奇数,假设位置为j,就将此奇数插入到i所指的位置,然后i往后移动一个位置,在插入之前,显然会涉及到数据的移动,也就是将[i,j-1]整体往后移动。直到整个数组遍历完毕,结束。

类似于题:把一个字符串的小写字母放到前面,大写放到后面,保持原有的顺序。

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        # write code here
        i = 0
        for j in range(len(array)):
            if array[j] % 2 == 1:
                tmp = array[j]
                for k in range(j - 1, i - 1, -1):
                    array[k + 1] = array[k]
                array[i] = tmp
                i += 1
        return array

扑克牌顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

题解
参考: https://www.nowcoder.com/questionTerminal/762836f4d43d43ca9deb273b3de8e1f4?answerType=1&f=discussion
这个题也不难,本来想的是用一堆判断来做,但是发现有更简单的思路。
分两种情况考虑,

  • 如果vector中不包含0的情况:那么如何判断呢?因为需要是顺子,所以首先不能有重复值, 如果没有重复值,那么形如[1 2 3 4 5],[5 6 7 8 9], 会发现最大值与最小值的差值应该小于5.
  • 如果vector中包含0:发现除去0后的值,判断方法和1中是一样的。

所以根据如上两个条件,算法过程如下:

  • 初始化一个set,最大值max_ = 0, 最小值min_ = 14
  • 遍历数组,对于大于0的整数,没有在set中出现,则加入到set中,同时更新max_, min_
  • 如果出现在了set中,直接返回false
  • 数组遍历完,最后再判断一下最大值与最小值的差值是否小于5
# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        if not numbers:
            return False
        mi, ma = 14, 0
        res = set()
        for x in numbers:
            if x in res:
                return False
            if x != 0 and x not in res:
                res.add(x)
                if x < mi:
                    mi = x
                if x > ma:
                    ma = x 
                    
        if ma - mi < 5:
            return True
        return False

两数之和 II - 输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

解法

  • 暴力解法:双层遍历,时间复杂度为 O(n^2),暴力解法没有充分利用原数组的性质 —— 有序
  • 当我们看到数列有序的时候,就应该想到可以用二分搜索法。遍历每个 nums[i],在剩余数组中查找 target-nums[i] 的值,时间复杂度为 O(n log n)。
  • 对撞指针法:首先判断首尾两项的和是不是 target,如果比 target 小,那么我们左边 (i)+1 位置的数(比左边位置的数大)再和右相相加,继续判断。如果比 target 大,那么我们右边 (j)-1 位置的数(比右边位置的数小)再和左相相加,继续判断。我们通过这样不断放缩的过程,就可以在 O(n) 的时间复杂度内找到对应的坐标位置。
class Solution(object):
    def twoSum(self, numbers, target):
        """
        :type numbers: List[int]
        :type target: int
        :rtype: List[int]
        """
        for i in range(len(numbers)):
            if numbers[i] > target:
                break
            x = target - numbers[i]
            l, r = i + 1, len(numbers) - 1
            while l <= r:
                mid = (l + r) // 2
                if numbers[mid] == x:
                    return [i + 1, mid + 1]
                if numbers[mid] > x:
                    r = mid - 1
                else:
                    l = mid + 1
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        
        idx1, idx2 = 0, len(numbers) - 1
        while idx1 < idx2:
            if numbers[idx1] + numbers[idx2] == target:
                return [idx1 + 1, idx2 + 1]
            elif numbers[idx1] + numbers[idx2] > target:
                idx2 -= 1
            else:
                idx1 += 1

缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。

示例 1:

输入: [1,2,0]
输出: 3
示例 2:

输入: [3,4,-1,1]
输出: 2
示例 3:

输入: [7,8,9,11,12]
输出: 1
说明:

你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。

解法
参考:https://blog.csdn.net/weixin_41958153/article/details/80950245
这道题的要求线性的复杂度和常数的空间,所以基本定下了一个总基调:1.一遍循环。2.原地修改
思路:非常tricky的想法。给我们的这个数组里,有负数,有正数,正数里还分大于(数组长度-1)的数和小于数组长度的数这两类。如果我们能把小于数组长度的数全部填入应该填入的位置(如果我们有1,它应该在0号位,如果我们有2,它应该在1号位,以此类推),那剩下的位置就留给那些负数和大于(数组长度-1)的数随机分配。这样一来,我么就会发现一些奇妙的东西。

比如[3,4,-1,1],按照我说的操作后,它应该是[1,-1,3,4],1,3,4全都在自己的位置上,剩下一个-1,它就是一个坑,这个坑本来应该是2(与index+1对应),所以缺失的第一个正数是2。总之,经过我们这样的操作后,所有能归位的元素都已经到了对应的位置上,一旦数组里的数字大小不等于对应的index+1时,这个点就是第一个缺的点,于是我们返回这个点的index+1即可。
如果数组里没有坑,那就是我们数组的长度是m,而元素是1…m时才有可能。这种时候第一个缺失的正数就是m+1。

class Solution:
    def firstMissingPositive(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #第一个循环去遍历Nums里的每一个元素
        for i in range(len(nums)):
            #如果当前这个数字在我们能够进行转移的范围内
            if 0 < nums[i] and nums[i]< len(nums):
                # if nums[i] != nums[nums[i]-1]:
                #如果当前这个位置上的数字与它的index对应不上的话,我们会把它放到他应该在的正确的位置上去
                #同时为了保证数组元素信息不丢失,那个正确位置上的信息我们也要拿过来。
                while nums[i] != i + 1:
                    nums[nums[i]-1], nums[i] = nums[i], nums[nums[i] - 1]
                    #一旦换完后当前位置i上的元素出现以下任意一种情况都说明我们不用再换了
                    if nums[i] <= 0 or nums[i] >= len(nums) or nums[i]==nums[nums[i]-1]:
                        break
     
        #上一段代码在干的事情就是我们让大于0小于len(nums)的元素全部到对应的位置上去
        #比如nums为[1,3,-1,5,2],那么经过上面之后就变成[1,2,3,-1,5],能够对应上的数字全部已经正确排序,缺陷的(-1)的index+1就是缺失的第一个正数。
        
        jet = 1
        
        #再次遍历数组
        #jet是一个标识符,用来标志我们找没找到不符合的项
        for i in range(len(nums)):
            #找到的第一个大小与index不符的项,我们的答案就已经产生,就是当前index+1
            if nums[i] != i + 1:
                jet = 0
                ans = i + 1
                break
        #如果数组是[1,2,3]这种,每一项都符合,则缺失的是4,也就是len(nums) + 1
        if jet == 1:
            ans = len(nums)+1
        return ans

存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true
示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true
示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

解法

  • 使用一个字典记录每个元素最近出现的位置,遍历一遍数组时,判断每个元素是否曾经出现过并且出现的位置和当前位置相差小于k
  • 结合使用滑动窗口和查找表,不断查找当前滑动窗口内有没有重复值。我们通过建立一个 record 查找表,表中存的是窗口中的数,另外我们要注意的是,当窗口的大小 > k 的时候,我们要移除 record 中最左边的元素(保证我们窗口中有 <= k 个数)

解法一:

class Solution(object):
    def containsNearbyDuplicate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        dic = {}
        for i in range(len(nums)):
            if nums[i] in dic and i - dic[nums[i]] <= k:
                return True
            dic[nums[i]] = i
                
        return False    
                

解法二:

class Solution:
    def containsNearbyDuplicate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """

        n = len(nums)

        if (n <= 1):
            return False

        recode = set()

        for i in range(n):
            if nums[i] in recode:
                return True
            recode.add(nums[i])
            if len(recode) > k:
                recode.remove(nums[i - k])

        return False

存在重复元素 III

在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。

如果存在则返回 true,不存在返回 false。

示例 1:

输入: nums = [1,2,3,1], k = 3, t = 0
输出: true
示例 2:

输入: nums = [1,0,1,1], k = 1, t = 2
输出: true
示例 3:

输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

解法
也是查找表与滑动窗口的思路:维持滑动窗的大小最大为 k,遍历每一个元素 nums[i],在活动窗口中寻找 |one-nums[i]| < t,即窗口中的元素范围为:[one-t … one+t] 之间

class Solution:
    def containsNearbyAlmostDuplicate(self, nums, k, t):
        """
        :type nums: List[int]
        :type k: int
        :type t: int
        :rtype: bool
        """

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

        recode = set()

        for i in range(n):
            if t == 0:
                if nums[i] in recode:
                    return True
            else:
                for one in recode:
                    if abs(nums[i] - one) <= t:
                        return True
            recode.add(nums[i])

            if (len(recode) > k):
                recode.remove(nums[i - k])

        return False

最长连续和小于s的长度

辅导课堂在推进质量建设,需要分析每堂直播课的用户报障数量。
当连续多个课程的报障数量之和大于一个数s的时候,系统会发出报警。小猿想知道最长连续的没有触发报警的课程数量。

n,s = list(map(int,input().strip().split()))
l = list(map(int, input().strip().split()))
  
left, right = 0, 1
course_sum = l[0]
res = 0
  
while left <= right and right < n:
    if course_sum <= s:
        res = max(res, right - left)
    else:
        course_sum -= l[left]
        left += 1
    course_sum += l[right]
    right += 1
  
print(res)

你可能感兴趣的:(leetcode,Python)