leetcode刷题日记----数组问题

缺失的第一个正数

题目描述:给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数

基本思路:(1))原地哈希,将数组视为哈希表,:就把 11 这个数放到下标为 00 的位置, 22 这个数放到下标为 11 的位置,按照这种思路整理一遍数组。然后我们再遍历一次数组,第 11 个遇到的它的值不等于下标的那个数,就是我们要找的缺失的第一个正数。这个思想就相当于我们自己编写哈希函数,这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置
(2)借助python的set,将列表转化为集合,再从1遍历到N+1,如果没有出现在set中,那么久返回这个数。

from typing import List


class Solution:

    # 3 应该放在索引为 2 的地方
    # 4 应该放在索引为 3 的地方

    def firstMissingPositive(self, nums: List[int]) -> int:
        size = len(nums)
        for i in range(size):
            # 先判断这个数字是不是索引,然后判断这个数字是不是放在了正确的地方
            while 1 <= nums[i] <= size and nums[i] != nums[nums[i] - 1]:
                self.__swap(nums, i, nums[i] - 1)
                #nums[nums[i]-1],nums[i] = nums[i],nums[nums[i]-1]
                #这里需要注意的是直接交换两个位置的值,须写成上面形式,写成
                #nums[i],nums[nums[i]-1]=nums[nums[i]-1],nums[i]将会超时,因为
                #这样先对nums[i]进行赋值,而后面nums[nums[i]-1]中的nums[i]就发生变化了
                

        for i in range(size):
            if i + 1 != nums[i]:
                return i + 1

        return size + 1

    def __swap(self, nums, index1, index2):
        nums[index1], nums[index2] = nums[index2], nums[index1]

情侣牵手

题目描述:N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。

人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。

这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

基本思路:(1)假设相邻的座位左边是正确的,我们查看右边是否是匹配的,如果不是,就在找到匹配的情侣与当前位置进行交换。

class Solution:
    def minSwapsCouples(self, row: List[int]) -> int:
        def find_another(n):
            if n%2 ==0:
                return n+1
            else:
                return n-1
        
        c = 0
        for i in range(0,len(row),2):
            p1 = row[i]
            p2 = find_another(p1)
            if row[i+1]!=p2:
                j = row.index(p2)
                row[i+1], row[j] = row[j], row[i+1]
                c += 1
        return c

(2)并查集:
如果有两对情侣坐错了位置,那么我们交换2-1次就可以了
如果有三对情侣坐错了位置,那么我们交换3-1次就可以了
同理,如果有k对情侣坐错了位置,那么我们需要交换k-1次
我们可以使用并查集来处理,每次遍历相邻的两个位置,如果他们本来就是情侣,他们处于大小为1的错误环中,只需要交换0次。如果不是情侣,说明他们呢两对处在同一个错误环中,我们将他们合并(union),将所有的错坐情侣合并和后,答案就是情侣对 - 环个数。
这也说明,最差的情况就是所有N对情侣都在一个环中,这时候我们需要N - 1调换。
最好情况每对情侣已经坐好了,已经有N个大小为1的环,这时候我们需要N - N次调换。

怎么通过代码确定出这里面的环是一个关键,可以这样做:每两个数字作为一个整体,对一个整体内的两个数字先除以2再unit,有什么效果呢?举个例子: 1 3 2 0 ,1和3先除以2,再unit,也就是unit(0,1) ; 2和0 ,同理unit(1,0),这样通过1 和 0 除2的结果相同这一点(或2 3),就把4个数字都算进一个环里去了。

class Solution:
    def minSwapsCouples(self, row: List[int]) -> int:
        f = {
     }

        def find(x):
            f.setdefault(x,x)
            while f[x]!=x:
                x = f[x]
            return x

        def union(x,y):
            f[find(x)] = find(y)

        for i in range(len(row)//2):
            p1 = row[2*i]
            p2 = row[2*i+1]
            union(p1//2,p2//2)
        return len(row)//2-len(set(map(find,f)))

长度最小的子数组

题目描述:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。

基本思路:
指针
二分加双指针
思路比较简单,直接看代码

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        #双指针
        # if sum(nums)
        
        # left, right, res, sum_r = 0, 0, float("inf"),0
        # while right
        #     while sum_r
        #         sum_r += nums[right]
        #         right += 1

        #     while sum_r>=s:
        #         res = min(res,right-left)
        #         sum_r -= nums[left]
        #         left += 1
        # return res

        #二分加双指针

        def helper(size):
            tem_sum = 0
            for i in range(len(nums)):
                tem_sum += nums[i]

                if i>=size:
                    tem_sum -= nums[i-size]
                if tem_sum>=s:
                    return True
            return False

        left, right = 0, len(nums)
        res = 0
        while left<=right:
            mid = left+(right-left)//2
            if helper(mid):
                res = mid
                right = mid-1
            else:
                left = mid+1
        return res

最长重复子数组

题目描述:给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出: 3
解释:
长度最长的公共子数组是 [3, 2, 1]。

基本思路:动态规划
设为dp[i][j]为A[i:]和B[j:]的最长公共前缀,那么很容易得到递推公式:
如果A[I]==B[j],dp[i][j]=dp[i+1][j+1]+1 否则为0,最后的答案就是dp[i][j]中的最大值。

代码如下:

class Solution:
    def findLength(self, A: List[int], B: List[int]) -> int:
        maxL = 0
        m, n = len(A), len(B)
        dp = [[0]*(n+1) for _ in range(m+1)]
        for i in range(m-1,-1,-1):
            for j in range(n-1,-1,-1):
                if A[i]==B[j]:
                    dp[i][j] = dp[i+1][j+1] + 1
                    maxL = max(maxL,dp[i][j])
                
        return maxL

数组中的逆序对

题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
基本思路:归并排序,归并排序过程中merge过程,就可以计算逆序对数,merge过程是两个有序数组进行合并,因此,可以很方便地计算逆序的对数。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        count=0#计数变量
        def merge_sort(alist):
            if len(alist) <= 1:
                return alist
            num = len(alist)//2
            left = merge_sort(alist[:num])
            right = merge_sort(alist[num:])
            return merge(left,right)


        def merge(left, right):
            nonlocal count
            l, r = 0, 0
            result = []
            while l<len(left) and r<len(right):
                if left[l] <= right[r]:
                    result.append(left[l])
                    l += 1
                else:
                    result.append(right[r])
                    r += 1
                    count+=len(left)-l
            result += left[l:]
            result += right[r:]
            return result
        merge_sort(nums)
        return count


移掉k位数字

题目描述:给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:

num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。

基本思路:删除一个数字使剩下的数字最大,且删除后数字的先后顺序跟原数组相同,那我们只能从左到右遍历去选择要删除的数字,如果num[i-1]

代码如下:

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        numStack = []
        
        # Construct a monotone increasing sequence of digits
        for digit in num:
            while k and numStack and numStack[-1] > digit:
                numStack.pop()
                k -= 1
        
            numStack.append(digit)
        
        # - Trunk the remaining K digits at the end
        # - in the case k==0: return the entire list
        finalStack = numStack[:-k] if k else numStack
        
        # trip the leading zeros
        return "".join(finalStack).lstrip('0') or "0"

拼接最大数

题目描述:给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。

输入:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
输出:
[9, 8, 6, 5, 3]

基本思路:这题跟之前的题目类似,只不过这里是要在两个数组找k个数字使拼接后的数字最大,这里新数组中的从同一数组中取出的数字要保持在原数组中的相对顺序。这里每个数组中取多少数字我们是不知道的,那么明显。这种需要枚举,在所有取法中找到最大值。在nums1中取i个数字,那么在nums2中只能去k-i个数字。
要使拼接后的数组最大,只要每个数组中取得数字最大。因此我们先单独计算每个数组能取到的最大数组,这个方法跟上面一题类似。然后再将两个数组拼接在一起。拼接可以参考归并排序merge,不过这里要注意两个指针所指元素相等的情况。具体看代码。

代码如下:

class Solution:
    def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
        def search_max(nums, k):
            stack = []
            drop = len(nums)-k
            for digit in nums:
                while drop and stack and stack[-1]<digit:
                    stack.pop()
                    drop -= 1
                stack.append(digit)
        
            return stack[:k]

        def merge(A, B):
            if len(A)>len(B):
                A, B = B, A
            #print("A:",A)
            #print("B:",B)
            res = []
            i, j = 0, 0
            while i<len(A) and j<len(B):
                if A[i]>B[j]:
                    res.append(A[i])
                    i += 1
                elif A[i]<B[j]:
                    res.append(B[j])
                    j += 1
                #两个元素相等,应该取数组较大的那个
                else:
                    if A[i:]>B[j:]:
                        res.append(A[i])
                        i += 1
                    else:
                        res.append(B[j])
                        j += 1
            if i<len(A):res += A[i:]
            else:res += B[j:]
            #print(res)
            return res
        return max(merge(search_max(nums1, i), search_max(nums2, k-i)) for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2))

去除重复字母

题目描述:给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

基本思路:首先需要去除字符串中的重复字母,那么只出现一次的字母是不可以被删除的。而出现一次以上的字母,我们怎样删除才能使最后的字母序最小呢。类似前几题,我们维护一个单调栈,
首先建立一个字典。其中 key 为 字符 c,value 为其出现的剩余次数。
从左往右遍历字符串,每次遍历到一个字符,其剩余出现次数 - 1.
对于每一个字符,如果其对应的剩余出现次数大于 1,我们可以选择丢弃(也可以选择不丢弃),否则不可以丢弃。
是否丢弃的标准和上面题目类似。如果栈中相邻的元素字典序更大,那么我们选择丢弃相邻的栈中的元素。

代码如下:

class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        numdict = {
     }
        for v in s:
            numdict[v] = numdict.get(v,0)+1
        stack = []
        for v in s:
            if v not in stack:
                while stack and v<stack[-1] and numdict[stack[-1]]>0:
                    stack.pop()
                stack.append(v)
            numdict[v] -= 1
            
        return "".join(stack)

乘积小于K的数组
题目描述:给定一个正整数数组 nums。找出该数组内乘积小于 k 的连续的子数组的个数。
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

说明:
0 < nums.length <= 50000
0 < nums[i] < 1000
0 <= k < 10^6

基本思路:双指针:当j向右移动,使得i到j满足条件,那么相对于之前已经找到的数组增加的就是从i到j,i+1到j,…,j-1到j,总共增加了j-i+1个数组满足条件。
1 当k<=1时,由于数组为正整数数组,明显不存在满足题意的连续子数组,故直接返回结果0。
2 令保存结果的变量res=0,滑动区间左指针left=0,保存滑动区间中元素乘积的变量pro为1。
3 令滑动区间右指针right从数组左到右滑动,每滑动1次,让pro乘以新元素。如果此时pro大于等于k,则滑动左指针left,每滑动一次令pro除以nums[left],得到新的滑动区间的元素乘积,直到pro 4 当right滑动到最右端后,res中便累加了以各个元素为子数组右端点时的有效子数组个数。返回res即可。

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        n = len(nums)
        if n==0 or k<=1:return 0
        res = 0
        i = 0
        prob = 1
        for j, val in enumerate(nums):
            prob *= val
            while prob>=k:
                prob = prob/nums[i]
                i += 1
            res += j-i+1
        return res

你可能感兴趣的:(leetcode刷题,算法,数据结构,python)