[总结]一些经典问题

在查找刷题攻略的时候,也遇到了一些比较经典、有趣的题目,记录在这里,不断更新。难度保持在LeetCode中的Medium级别左右。

1.求一个数组中右边第一个比他大的数

单调栈专用于解决此类问题。其中有一个trick是,查找比他大的数用单调递减栈,查找比他小的数用单调递增栈。

class Soluation(object):
    def findFirstBiggerNum(self,nums):
        if not nums:return
        stack = []
        res = [-1 for i in range(len(nums))]
        for i in range(len(nums)):
            while stack and nums[i] > nums[stack[-1]]:
                res[stack.pop()] = nums[i]
            stack.append(i)
        return res

nums = [9,6,5,7,3,2,1,5,9,10]
Soluation().findFirstBiggerNum(nums)

2.牛的视野

变形的单调栈问题。实际上是求每个数距离右边第一个比他大的数的距离。

class Soluation(object):
    def badHairDay(self,nums):
        if not nums:return
        stack = []
        res = 0
        for num in nums:
            while stack and num > stack[-1]:
                stack.pop()
            res+=len(stack)
            stack.append(num)
        return res

nums = [10,3,7,4,12,2]
Soluation().badHairDay(nums)

3.找出一个数组的中位数,即左边的数都比它小,右边的都比它大

两头扫策略。分别做两个辅助数组,从右到左遇到的最小数,以及从左到右遇到的最大数,如果在一个索引上,这两个值相同,则说明是该数组‘中位数’。

class Soluation(object):
    def findNumBiggerLeftSmallerRight(self,nums):
        if not nums:return
        rightmin = [nums[-1] for i in range(len(nums))]
        for i in reversed(range(len(nums)-1)):
            if nums[i] < rightmin[i+1]:
                rightmin[i] = nums[i]
            else:
                rightmin[i] = rightmin[i+1]
        leftmax = nums[0]
        for i in range(0,len(nums)):
            if nums[i] > leftmax :
                leftmax  = nums[i]
            if leftmax  == rightmin[i]:
                return nums[i]
        return -1

nums = [7, 10, 2, 6, 19, 22, 32]
Soluation().findNumBiggerLeftSmallerRight(nums)

4.n个骰子的点数

一道递归求解题。

def getNSumCount(n,sum):
    if n<1 or sum<n or sum>6*n:
        return 0
    if n==1:
        return 1
    res = 0
    for i in range(6):
        res += getNSumCount(n-1,sum-(i+1))
    return res

def PrintProbability(number):
    res = 0
    for i in range(number,6*number+1):
            res += getNSumCount(number,i)
    for i in range(number,6*number+1):
        ratio = getNSumCount(number,i)/res
        print("{}: {:e}".format(i, ratio))

s = PrintProbability(5)

5.最长公共子串(The Longest Common Substring)

字串必须是连续的,例如对于'abcdfg','abdfg',其解为‘dfg’,最大长度为3。使用动态规划求解。

#最长公共子串(The Longest Common Substring)
class LCS(object):
    def find_lcsubstr(self,s1,s2):
        m,n = len(s1),len(s2)
        dp = [[0 for j in range(n+1)] for m in range(m+1)]
        maxLength = 0
        index = 0
        for i in range(1,m+1):
            for j in range(1,n+1):
                if i == 0 or j==0:
                    dp[i][j] = 0
                elif s1[i-1] == s2[j-1]:
                    dp[i][j] = dp[i-1][j-1]+1
                    if dp[i][j]>maxLength:
                        maxLength = dp[i][j]
                        index = i
        return s1[index-maxLength:index],maxLength

print(LCS().find_lcsubstr('abcdfg','abdfg'))

6.最长公共子序列 (The Longest Common Subsequence)

与字串不同,子序列可以是不连续的,例如对于'abcdfg','abdfg',其解为‘abdfg’,最大长度为5。使用动态规划求解,时间复杂度\(O(N^2)\)。(若只求长度,可以用栈+二分求解,时间复杂度可以达到\(O(NlogN)\)

def find_lcseque(s1, s2):
    m,n = len(s1),len(s2)
    dp = [[0 for j in range(n+1)] for i in range(m+1)]
    dp_d = [[None for j in range(n+1)] for i in range(m+1)]
    for i in range(1,m+1):
        for j in range(1,n+1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = 1+dp[i-1][j-1]
                dp_d[i][j] = 'ok'
            else:
                dp[i][j] = max(dp[i][j-1],dp[i-1][j])
                if dp[i][j-1]>dp[i-1][j]:
                    dp_d[i][j] = 'left'
                else:
                    dp_d[i][j] = 'up'
    res = ''
    while dp_d[m][n]:
        if dp_d[m][n] == 'ok':
            res+=s1[m-1]
            m-=1
            n-=1
        elif dp_d[m][n] == 'left':
            n-=1
        elif dp_d[m][n] == 'up':
            m-=1
    return res[::-1]

find_lcseque('abdfg','abcdfg')

7.在平面上找距离最近的两个点

参考平面中距离最近点对。使用分治的思想求解。

#求出平面中距离最近的点对(若存在多对,仅需求出一对)
import random
import math

#计算两点的距离
def calDis(seq):
    dis=math.sqrt((seq[0][0]-seq[1][0])**2+(seq[0][1]-seq[1][1])**2)
    return dis

#生成器:生成横跨跨两个点集的候选点
def candidateDot(u,right,dis,med_x):
    #遍历right(已按横坐标升序排序)。若横坐标小于med_x-dis则进入下一次循环;若横坐标大于med_x+dis则跳出循环;若点的纵坐标好是否落在在[u[1]-dis,u[1]+dis],则返回这个点
    for v in right:
        if v[0]>med_x+dis:
            break
        if v[1]>=u[1]-dis and v[1]<=u[1]+dis:
            yield v

#求出横跨两个部分的点的最小距离
def combine(left,right,resMin,med_x):
    dis=minDis=resMin[1]
    pair=resMin[0]
    for u in left:
        if u[0]<med_x-dis:
            continue
        for v in candidateDot(u,right,dis,med_x):
            dis=calDis([u,v])
            if dis<minDis:
                minDis=dis
                pair=[u,v]
    return [pair,minDis]


#分治求解
def getMinDisPair(seq):
    #求序列元素数量
    n=len(seq)
    seq=sorted(seq)
    #递归开始进行
    if n<=1:
        return None,float('inf')
    elif n==2:
        return [seq,calDis(seq)]
    else:
        half=n//2
        if n%2 == 1:
            med_x = seq[half][0]
        else:
            med_x = (seq[half][0]+seq[half+1][0])/2
        left=seq[:half]
        resLeft=getMinDisPair(left)
        right=seq[half:]
        resRight=getMinDisPair(right)
        #获取两集合中距离最短的点对
        if resLeft[1]<resRight[1]:
            d = resLeft
        else:
            d = resRight
        resMin=combine(left,right,d,med_x)
        pair=resMin[0]
        minDis=resMin[1]
    return [pair,minDis]

seq=[(random.randint(0,10000),random.randint(0,10000)) for x in range(500)]
print("优化算法",getMinlengthPair(seq))

8.背包问题

背包问题说的是,给定背包容量W,一系列物品{weiht,value},每个物品只能取一件,获取最大值。

class Solution():
    def bag(self,w,v,weight):
        n = len(w)
        dp = [[0 for j in range(weight+1)] for i in range(n+1)]
        for i in range(1,n+1):
            for j in range(1,weight+1):
                if w[i-1]>j:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
        return dp[-1][-1]

    # 压缩空间
    def solve2(vlist,wlist,totalWeight,totalLength):
        resArr = np.zeros((totalWeight)+1,dtype=np.int32)
        for i in range(1,totalLength+1):
            for j in range(totalWeight,0,-1):
                if wlist[i] <= j:
                    resArr[j] = max(resArr[j],resArr[j-wlist[i]]+vlist[i])
        return resArr[-1]

v = [60,100,120]
w = [10,20,30]
weight = 50
Solution().bag(w,v,weight)

9.实现一个计算器(包括+-*/和括号)

与LeetCode上的题不同,这里需要同时加上+-*/和括号,可以使用栈+递归解决。


def getRes(resSoFar, sign, num):
     if sign == '+':
         return resSoFar + num
     elif sign == '-':
         return resSoFar - num
     elif sign == '*':
         return resSoFar * num
     elif sign == '/':
         return int(resSoFar / num)

def calculate(s: str) -> int:
     sign =  '+'
     num = 0
     resCur = 0
     res = 0
     n = len(s)
     i = 0
     while i < n:
         ch = s[i]
         if ch.isdigit():
             num = num*10 + int(ch)
         elif ch == '(':
             cnt = 1
             j = i + 1
             while j < n:
                 if s[j] == '(':
                     cnt += 1
                 elif s[j] == ')':
                     cnt -= 1
                 if cnt == 0:
                     num = calculate(s[i+1:j])
                     break
                 j += 1
             i = j
         if ch in "+-*/" or i == n - 1:
             resCur = getRes(resCur, sign, num)
             if ch in "+-" or i == n - 1:
                 res += resCur
                 resCur = 0
             sign = ch
             num = 0
         i += 1

     return res

print(calculate('2+3*4'))

10.寻找数组中最大最小值

分治解决。

class Solution:
    def __init__(self):
        self.max_res,self.min_res = -float('inf'),float('inf')
    def getMaxMin(self,nums):
        if not nums:return
        self.helper(nums,0,len(nums)-1)
        return self.max_res,self.min_res
    def helper(self,nums,left,right):
        if right <= left+1:
            if nums[left] < nums[right]:
                self.max_res = max(self.max_res,nums[right])
                self.min_res = min(self.min_res,nums[left])
            else:

                self.max_res = max(self.max_res,nums[left])
                self.min_res = min(self.min_res,nums[right])
        else:
            mid = left+((right-left)>>1)
            self.helper(nums,left,mid)
            self.helper(nums,mid,right)


nums = [2,3,4,5,3,8,10,1]
Solution().getMaxMin(nums)

11.给定一个数组,值可以为正、负和0,请返回累加和为给定值k的最长子数组长度

循环整个数组,采用hash存储索引i和到i时总和sun。那么,只要有sum-k在hash表中,即说明hash[sum-k]...i总和为k。

class Solution:
    def maxLength(self,nums,k):
        dic = {}
        dic[0] = -1  #代表没有记录的时候index
        res = sum_ = 0
        for i in range(len(nums)):
            sum_ += nums[i]
            if sum_-k in dic:
                res  = max(res,i-dic[sum_-k])
            if sum_ not in dic:
                dic[sum_] = i
        return res



nums = [2,3,-1,1,3,4,2]
k = 6
Solution().maxLength(nums,k)

12.给定一个数组,值可以为正、负和0,请返回累加和小于等于k的最长子数组长度

参考blog。最终时间复杂度可以达到O(N)。
分两步解决:

  • 第一步:获取以每个位置开头的最小和的长度。
  • 第二步:从0到N逐一判断刚才最小长度是否可以合并在一起达到小于等于k的效果。
#---> 可以O(N)
class Solution:
    def maxLength(self,nums,k):
        min_value = [0 for i in range(len(nums))]
        max_index = [0 for i in range(len(nums))]
        for i in reversed(range(len(nums))):
            if i==len(nums)-1:
                min_value[i] = nums[i]
                max_index[i] = i
            else:
                if min_value[i+1] <= 0:
                    min_value[i] = nums[i]+min_value[i+1]
                    max_index[i] = max_index[i+1]
                else:
                    min_value[i] = nums[i]
                    max_index[i] = i
        sum_ = 0
        start = end = 0
        res = 0
        for i in range(len(nums)):
            while end < len(nums) and sum_ + min_value[end]<=k:
                sum_ += min_value[end]
                end = max_index[end]+1
            if end > i:
                sum_ -= nums[i]
            res = max(res, end - i)
            end = max(end, i + 1)
        return res


nums = [2,3,-1,1,3,4,2]
k = 6
Solution().maxLength(nums,k)

13.实现栈中元素逆序

递归实现。

class Solution:
    def reverseStack(self,stack):
        if not stack:
            return
        i = self.getAndRemoveLastElement(stack)
        self.reverseStack(stack)
        stack.append(i)
        return stack

    def getAndRemoveLastElement(self,stack):
        i = stack.pop()
        if not stack:
            return i
        else:
            last = self.getAndRemoveLastElement(stack)
            stack.append(i)
            return last


nums = [1,2,3,4,5]
Solution().reverseStack(nums)

14.小和问题

即对于每个数求左边比他小的数的总和。
例如数组【1,3,5,2,4,6】得小和为0+1+4+1+6+15=27
【1,3,5,4,2,6】得小和为0+1+4+4+1+15=25
可以用分治的思想解决。

class Solution:
    def getSmallSum(self,nums):
        if not nums:
            return 0
        return self.func(nums,0,len(nums)-1)

    def func(self,s,lo,hi):
        if lo == hi:
            return 0
        mid = lo+(hi-lo)//2
        return self.func(s,lo,mid)+self.func(s,mid+1,hi)+self.merge(s,lo,mid,hi)

    def merge(self,a,lo,mid,hi):
        smallSum = 0
        i,j = lo,mid+1
        aux = a[:]
        for k in range(lo,hi+1):
            if  i>mid:
                a[k] = aux[j]
                j+=1
            elif  j>hi:
                a[k] = aux[i]
                i+=1
            elif  aux[j] < aux[i]:
                a[k] = aux[j]
                j+=1
            else:
                smallSum +=  aux[i] * (hi-j+1)
                a[k] = aux[i]
                i+=1
        return smallSum

nums = [1,3,5,4,2,6]
Solution().getSmallSum(nums)

15.逆序对

分治求解。

class Solution():
    def InversePairs(self, data):
        if not data:
            return 0
        return self.func(data,0,len(data)-1) %1000000007

    def func(self,s,lo,hi):
        if hi <= lo:return 0
        mid = lo+(hi-lo)//2
        return self.func(s,lo,mid)+self.func(s,mid+1,hi)+self.merge(s,lo,mid,hi)


    def merge(self,a,lo,mid,hi):
        smallSum = 0
        i,j = lo,mid+1
        aux = a[:]
        for k in range(lo,hi+1):
            if  i>mid:
                a[k] = aux[j]
                j+=1
            elif  j>hi:
                a[k] = aux[i]
                i+=1
            elif  aux[j] < aux[i]:
                smallSum +=  mid-i+1
                a[k] = aux[j]
                j+=1
            else:
                a[k] = aux[i]
                i+=1
        return smallSum



nums = [1,2,3,4,5,6,7,0]
Solution().InversePairs(nums)

16.子矩阵累加和

实际上可以转化为数组最大累加和问题。

#O(n^2 * m)
class Solution():
    def maxMatrix(self,matrix):
        m,n = len(matrix),len(matrix[0])
        res = -float('inf')
        for i in range(m):
            nums = matrix[i]
            res = max(res,self.maxSubArray(nums))
            for j in range(i+1,m):
                for k in range(n):
                    nums[k] += matrix[j][k]
                res = max(res,self.maxSubArray(nums))
        return res


    def maxSubArray(self, nums):
        if not nums:
            return 0

        curSum = maxSum = nums[0]
        for i in range(1,len(nums)):
            curSum = max(nums[i], curSum + nums[i])
            maxSum = max(maxSum, curSum)

        return maxSum

matrix = [[1,2,3,4,5,6,7,0]]
Solution().maxMatrix(matrix)

17.给定一个数组,长度大于2.找出不相交的两个子数组,求最大的和。

可以求两个辅助数组,即从左到右的最大连续子数组和,和从右到左的最大连续子数组和,最后将两个子数组根据索引联合求最大和即可。

class Solution():
    def TwoSubarrayMaxSum(self,nums):
        N = len(nums)
        maxLeft = [0 for i in range(N)]
        maxRight = [0 for i in range(N)]
        maxsum = cur = 0
        for i in range(N):
            maxsum = max(maxsum+nums[i],nums[i])
            cur = max(maxsum,cur)
            maxLeft[i] = cur
        maxsum = cur = 0
        for i in reversed(range(N)):
            maxsum = max(maxsum+nums[i],nums[i])
            cur = max(maxsum,cur)
            maxRight[i] = cur
        res = 0
        for i in range(N-1):
            res = max(res,maxLeft[i]+maxRight[i+1])
        return res

nums =    [1,2,0,4,-1]
Solution().TwoSubarrayMaxSum(nums)

18.给定一个数组,划分为两部分,求左部分中的最大值减去右部分的最大值的绝对值中,最大是多少?

因为左部分最大值和右部分最大值必然包括全局最大值,左部分中的最大值减去右部分的最大值的绝对值可以看做是全局最大值减去左部分最大值或者是全局最大值减去右部分最大值。又因为且左部分最大值必然比第一个数大,右部分最大值必然比最后一个数大。这种情况下,左部分最大值取第一个数最小,右部分最大值取最后一个数最小。

class Solution:
    def getMaxMeign(self,nums):
        if not nums:
            return
        max_num = max(nums)
        min_num = min(nums[0],nums[-1])
        return max_num- min_num

nums = [4,7,2,3,5]
Solution().getMaxMeign(nums)

19.给定数组arr和整数num,共返回有多少个子数组满足如下情况:max(arr[i..j])-min(arr[i..j]) <= num 其中j>=i

使用双端队列 求滑动窗口内的最大值最小值
两个结论:

  • 1、一个数组满足最大值-最小值<=k,则该数组得子数组一定满足最大值-最小值<=k
  • 2、一个子数组不满足最大值-最小值<=k,则包含该子数组的数组一定不满足最大值-最小值<=k
#O(N),L,R均不回退
class Solution():
    def getRes(self,nums,k):
        if not nums:
            return
        ma = []
        mi = []
        res = l = r = 0
        #l----r的窗口,求最大值最小值
        while l<len(nums):
            while r<len(nums):
                while ma and nums[r] >= nums[ma[-1]]:
                    ma.pop()
                while mi and nums[r] <= nums[mi[-1]]:
                    mi.pop()
                ma.append(r)
                mi.append(r)

                if nums[ma[0]] - nums[mi[0]] > k:
                    break
                r+=1
            if ma[0] == l:
                ma.pop(0)
            if mi[0] == l:
                mi.pop(0)
            res += r-l
            l+=1
        return res



nums = [4,7,2,3,5]
Solution().getRes(nums,2)

20.求两个单链表相交的第一个节点,要求时间复杂度O(N+M),空间O(1)

分三种情况:

  • 两个链表都无环
  • 一个链表有环,一个链表无环,不可能相交
  • 两个链表都有环

class Solution():
    def getRes(self,head1,head2):
        loop1 = self.detectCycle(head1)
        loop2 = self.detectCycle(head2)
        if not loop1 and not loop2:
            return self.getIntersectionNode(head1,head2)
        if loop1 and loop2:
            return self.getSameNodeFromLoop(head1,loop1,head2,loop2)
        return


    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        node = headA
        l1 = l2 = 0
        while node:
            node = node.next
            l1 += 1
        node = headB
        while node:
            node = node.next
            l2 += 1
        if l1>l2:
            for i in range(l1-l2):
                headA = headA.next
        if l1<l2:
            for i in range(l2-l1):
                headB = headB.next
        while headA and headA!=headB:
            headA = headA.next
            headB = headB.next
        return headA


    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        # if not pHead or not pHead.next:return
        if not head or not head.next:
            return
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                tmp = head
                while tmp != slow:
                    tmp = tmp.next
                    slow = slow.next
                return tmp
        return

    def getSameNodeFromLoop(self,head1,loop1,head2,loop2):
        if loop1 == loop2:
            return self.getIntersectionNodeFromLoop(head1, head2,loop)
        node = loop1.next
        while node!=loop1:
            if node == loop2:
                return [loop1,loop2]
            node = node.next
        return



    def getIntersectionNodeFromLoop(self, headA, headB,loop):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        node = headA
        l1 = l2 = 0
        while node!=loop:
            node = node.next
            l1 += 1
        node = headB
        while node!=loop:
            node = node.next
            l2 += 1
        if l1>l2:
            for i in range(l1-l2):
                headA = headA.next
        if l1<l2:
            for i in range(l2-l1):
                headB = headB.next
        while headA!=headB and headA!=loop:
            headA = headA.next
            headB = headB.next
        return headA

21.扇形染色问题

一个圆分成N个扇形,用m种颜色上色,要求相邻两个颜色不同,求有多少种不同的方法。
参考染色问题——扇形涂色,使用公式推导法,推导出求解公式,直接代入公式进行计算就可以。推导如下:
首先,设A(n)为最后的结果。那么
对第 A1 块扇形,有m种画法,
对第 A2 块扇形,有m-1种画法,
对第 A3 块扇形,有m-1种画法,
(因为只要求相邻的两块颜色不同,所以,只需要看前边的一块就可以了)
…………
…………
对第 An 块扇形,有m-1种画法,
即为 \(m∗(m−1)^{n−1}\)
但是这时候要分成两类:
1、\(A_n\)\(A_1\)不同色;
2、\(A_n\)\(A_1\)同色。
当颜色不同时符合题目要求,没有影响,
  而当两块相同时,可以将两块看作是一块,这个时候染色方法总数就是\(A(n−1)\),这里可能会有一点点绕,可以想象一下,前面的组合排列方式不变,头尾两块变成了一块,我们需要把这种情况的组合都排除出去。
则,\(A(n)+A(n−1)=m∗(m−1)^{n−1}\)
两边同时减去\((m−1)^n\),
\(A(n)−(m−1)^n=−[A(n−1)−(m−1)^{n−1}]\),

\[A(n)−(m−1)^n=−[A(n−1)−(m−1)^{n−1}] \\ =(−1)^2[A(n−2)−(m−1)^{n−2}] \\ =(−1)^3[A(n−3)−(m−1)^{n−3}] \\ =........ \\ =(−1)^{n−2}[A(2)−(m−1)^2] \\ (只剩两块时,m种颜色,共有A(2)=m∗(m−1)种) \\ =(−1)^{n−2}[m∗(m−1)−(m−1)^2] \\ =(−1)^n(m−1) \]

故,\(A(n)=(−1)^n(m−1)+(m−1)^n\)

推导完成,后面的代码反而不重要了。在这种情境中,数学推导能力是非常重要的。

22.机器人行走问题

一排有N个位置,一个机器人在最开始停留在P位置,如果\(P==0\)位置,下一分钟机器人一定向右移动到1位置;如果P==N-1,下一分钟机器人一定向左移动到N-2位置,求K分钟的时候,机器人到达T位置有多少中走法.
通过动态规划解得。重点在于掌握从递归到动态规划的修改过程。

#通过修改递归->动态规划
class Solution():
    def getres1(self,N,P,K,T):
        if N<2 or P<0 or P>=N or K<1 or T<0 or T>=N:
            return 0
        if K==1:
            return P==T
        if T == 0:
            return self.getres1(N,P,K-1,T+1)
        if T == N-1:
            return self.getres1(N,P,K-1,T-1)
        else:
            return self.getres1(N,P,K-1,T-1) + self.getres1(N,P,K-1,T+1)

    #优化:动态规划
    def getres2(self,N,P,K,T):
        if N<2 or P<0 or P>=N or K<1 or T<0 or T>=N:
            return 0
        dp = [[0 for j in range(N)] for i in range(K)]
        for i in range(K):
            for j in range(N):
                if i == 0:
                    dp[i][j] = int(j==P-1)
                elif j==0:
                    dp[i][j] = dp[i-1][j+1]
                elif j==N-1:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]
        print('dp',dp)
        return dp[-1][T-1]
Solution().getres1(5,2,4,3)

23.堆棋子

参考牛客原题,小易将n个棋子摆放在一张无限大的棋盘上。第i个棋子放在第x[i]行y[i]列。同一个格子允许放置多个棋子。每一次操作小易可以把一个棋子拿起并将其移动到原格子的上、下、左、右的任意一个格子中。小易想知道要让棋盘上出现有一个格子中至少有i(1 ≤ i ≤ n)个棋子所需要的最少操作次数.
关键在于不用尝试所有点,只需要尝试xs和ys的组合

def getdis(x1,y1,x2,y2):
    return abs(x2-x1)+abs(y2-y1)

def getRes(n,xs,ys):
    res = [float('inf') for i in range(n)]
    for x in xs:
        for y in ys:
            dis = []
            for i in range(n):
                dis.append(getdis(x,y,xs[i],ys[i]))
            heapq.heapheaify(dis)
            val = 0
            for i in range(n):
                val += heapq.heappop(dis)
                res[i] = min(res[i],val)
    return res

24.小易喜欢的队列

参考牛客原题,小易非常喜欢拥有以下性质的数列:
1、数列的长度为n
2、数列中的每个数都在1到k之间(包括1和k)
3、对于位置相邻的两个数A和B(A在B前),都满足(A <= B)或(A mod B != 0)(满足其一即可)
例如,当n = 4, k = 7
那么{1,7,7,2},它的长度是4,所有数字也在1到7范围内,并且满足第三条性质,所以小易是喜欢这个数列的
但是小易不喜欢{4,4,4,2}这个数列。小易给出n和k,希望你能帮他求出有多少个是他会喜欢的数列。

写出递归 画出状态二维表,从而得出最终转移方程

#写出递归 画出状态二维表,从而得出最终转移方程
def num1(n,k):
    return process(1,n,k)  #假设第0个位置为1,对后面无约束

def process(pre,n,k):
    if n==0:
        return 1    #当无需要尝试的个数n时,返回1个可能
    sum_ = 0
    for cur in range(pre,k+1):
        sum_ += process(cur,n-1,k)

    for cur in range(1,pre):
        if pre%cur != 0:
            sum_ += process(cur,n-1,k)
    return sum_


def num2(n,k):
    dp = [[0 for j in range(n)]for i in range(k+1)]
    for i in range(k+1):
        dp[i][0]=1
    for col in range(1,n):
        for row in range(1,k+1):
            sum_ = 0
            for cur in range(row,k+1):
                sum_ += dp[cur][col-1]
            for cur in range(1,row):
                sum_ += dp[cur][col-1] if (row%cur)!=0 else 0
            dp[row][col] = sum_
    res = 0
    for i in range(k+1):
        res += dp[i][n-1]
    return res



def num3(n,k):
    dp = [[0 for j in range(n)]for i in range(k+1)]
    for i in range(k+1):
        dp[i][0]=1
    for col in range(1,n):
        sum_ = 0
        for row in range(1,k+1):
            sum_ += dp[row][col-1]
        for row in range(1,k+1):
            noInclude = 0
            for cur in range(2*row,k+1,row):
                noInclude += dp[cur][col-1]
            dp[row][col] = sum_-noInclude
    res = 0
    for i in range(k+1):
        res += dp[i][n-1]
    return res


#优化内存
def num4(n,k):
    dp = [1 for i in range(k+1)]

    for col in range(1,n):
        sum_ = 0
        for row in range(1,k+1):
            sum_ += dp[row]
        for row in range(1,k+1):
            noInclude = 0
            for cur in range(2*row,k+1,row):
                noInclude += dp[cur]
            dp[row] = sum_-noInclude
    res = 0
    for i in range(1,k+1):
        res += dp[i]
    return res

25.切金条问题(哈夫曼编码)

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60. 金条要分成10,20,30三个部分。如果,先把长度60的金条分成10和50,花费60 再把长度50的金条分成20和30,花费50一共花费110铜板。但是如果先把长度60的金条分成30和30,花费60 再把长度30金条分成10和20,花费30 一共花费90铜板。输入一个数组,返回分割的最小代价。
贪心,利用哈夫曼原理可知,如果是要分成10, 20, 30,那么我先把10, 20加起来需要30代价(也就是30切成10,20),接着把加起来的30代价和原有的30加起来就是60代价,30+60代价就是90代价。也就是从树的顶端往下看,先是60的金条,现在先分成最大的两部分,30和30,需要60代价,接着需要其中一个30分割成10, 20,这个也需要10+20=30代价,那么一共就是90代价

import heapq

class Solution:
    def lessMoney(self,nums):
        heapq.heapify(nums)
        sum_ = cur = 0
        while len(nums)>1:

            cur  = heapq.heappop(nums)+heapq.heappop(nums)
            sum_ += cur
            heapq.heappush(nums,cur)
#             heapq.heapify(nums)
        return sum_

nums = [2,2,3,4,7]
Solution().lessMoney(nums)

26.二维平面点集合,求右上角没有点的所有点集合

通过排序策略降低复杂度,注意两种情况:是否有x轴重复的点。

class Solution:
    # 没有x轴重复的点
    def getRes1(self,points):
        points.sort(key=lambda x:x[0])   #对x轴排序
        res = []
        max_y = 0
        for point in points[::-1]:
            if point[1] >= max_y:
                res.append(point)
            max_y = max(max_y,point[1])
        return res

    # 假设有x轴,y轴重复的点,假设只有x,y轴都大于这个点的是在右上角的点(共线不算)
    def getRes2(self,points):
        points.sort(key=lambda x:(x[0],-x[1]))   #对x轴从小到大排序,对y轴从大到小排序
        res = []
        max_y = 0
        for point in points[::-1]:
            if point[1] >= max_y:
                res.append(point)
            max_y = max(max_y,point[1])
        return res

points=[[0,3],[1,6],[2,7],[3,2],[4,5],[5,4]]
Solution().getRes1(points)

points2=[[0,3],[1,6],[2,7],[3,2],[4,5],[4,6],[4,7],[5,4]]
Solution().getRes2(points2)

类似的还有俄罗斯信封问题。

27.求最大f()的子数组

f为数组中最小值 * sum(数组) ,不存在负数

class Solution:
    def getMaxf(self,nums):
        #求以每个点为最小值时,左右两边最大的扩张;左右两边第一个小于它的数
        if not nums:return
        res = -1
        leftMin,rightMin = self.getMinLR2(nums)
        for i in range(len(nums)):
            l = leftMin[i] if leftMin[i]!=-1 else -1
            r = rightMin[i] if rightMin[i]!=-1 else len(nums)
#             print(sum(nums[l+1:r])*nums[i])
            res = max(res,sum(nums[l+1:r])*nums[i])
        return res



    # 修改单调栈
    def getMinLR2(self,nums):
        stack = []
        leftMin = [-1 for i in range(len(nums))]
        rightMin = [-1 for i in range(len(nums))]
        for i in range(len(nums)):
            while stack and nums[i]<=nums[stack[-1]]:   #小于等于都需要弹出
                index = stack.pop()
                rightMin[index] = i
                if stack:
                    leftMin[index] = stack[-1]
            stack.append(i)
        while stack:
            index = stack.pop()
            if stack:
                leftMin[index] = stack[-1]
        return leftMin,rightMin

        #找小于的数 递增栈     利用单调栈可以求出每个元素右边/左边最近的大于/小于的值,但不能出现重复元素
        def getMinLR(self,nums):
            stack = []
            leftMin = [-1 for i in range(len(nums))]
            rightMin = [-1 for i in range(len(nums))]
            for i in range(len(nums)):
                while stack and nums[i]<nums[stack[-1]]:
                    index = stack.pop()
                    rightMin[index] = i
                    if stack:
                        leftMin[index] = stack[-1]
                stack.append(i)
            while stack:
                index = stack.pop()
                if stack:
                    leftMin[index] = stack[-1]
            return leftMin,rightMin

求子数组问题,可以计算以每个元素结尾/开头,或者每个元素满足某一条件的最值,最终遍历每个元素,求出子数组的最值。
单调栈可以求出每个元素右边/左边最近的大于/小于的值,但不能出现重复元素,在这道题中,可以使元素大于/小于等于时同样弹出,其第一个元素的范围出错,但是后面的元素范围不会出错,不会影响最后的结果 不影响最优解,例如45345345。

28.俄罗斯套娃信封问题

在信封外面套信封,当一个信封长度和高度都大于该信封则可以套,问最多套多少层。
从两种情况来讨论这个问题:

  • w无重复值(即信封的宽度每个信封都不一样)
    • 对w排序,对h数组求最长递增子序列
  • w,h可以重复(即信封的宽度存在一样的)
    • 对w从小到大排序,对h从大到小排序,
class Solution:
    # 这个问题主要在两个地方,一个是最长递增子序列的优化,二是当信封宽度相同时,信封高度h逆序排列。
    def maxEnvelopes(self,Envelopes):
        Envelopes.sort(key=lambda x:(x[0],-x[1]))   #对w从小到大排序,对h从大到小排序,
        hs = [x[1] for x in Envelopes]
        return self.LIS(hs)



    def LIS(self,nums):
        stack = []
        for num in nums:
            if not stack or num > stack[-1]:
                stack.append(num)
            else:
                lo = self.getlo(stack,num)
                stack[lo] = num
        return len(stack)


    #第一个不小于target的数
    def getlo(self,nums,target):
        lo,hi = 0,len(nums)-1
        while lo<=hi:
            mid = lo+(hi-lo)//2
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                hi = mid - 1
            else:
                lo = mid + 1
        return lo

29.设计TopKRecord结构

设计TopKRecord结构,可以不断向其中加入字符串,并且可以随时打印加入次数topK的字符串,使得

  • 在任何时刻,add方法时间复杂度不超过O(log K)
  • 在任何时刻,printTopK方法的时间复杂度不超过O(k)
class Node():
    def __init__(s,t):
        self.str = s
        self.times = t

#数据结构的配合
class TopKRecord():
    def __init__(self,k):
        map1 = {}               # count
        minheap = []            # 小跟堆
        map3 = {}               # 每个数在minheap中的位置,-1表示不在堆上
        index = 0

    def add(self,s):
        preIndex = -1
        if s not in map1:
            curNode = Node(s,1)
            map1[s] = curNode
            map3[curNode] = -1
        else:
            curNode = map1[s]
            curNode.times+=1
            preIndex = map3[curNode]
        if preIndex == -1:
            if index == len(minheap):
                if minheap[0].times<curNode.times:
                    map3[heap[0]] = -1
                    map3[curNode] = 0
                    minheap[0] = curNode
                    heapify(0,index)
            else:
                map3[curNode] = index
                minheap[index] = curNode
                heapInsert(index)
                index+=1
        else:
            heapify(preIndex,index)

30.两个有序数组相加和的TOP K问题

与[leetcode]Leetcode 373.Find K Pairs with Smallest Sums相同,这里使用了堆解决。

import heapq

class Node():
    def __init__(self,l1,l2,arr1,arr2):
        self.l1 = l1
        self.l2 = l2

        self.val1 = arr1[self.l1]
        self.val2 = arr2[self.l2]
    def __lt__(self,others):
        if self.val1+self.val2 < others.val1+others.val2:
            return True
        elif self.val1+self.val2 > others.val1+others.val2:
            return False

# 不断弹出,解决去重问题
class Solution():
    def topKSum(self,arr1,arr2,k):
        if not k:
            return []
        seen = set()
        res = []
        l1,l2 = len(arr1)-1,len(arr2)-1
        topNode = Node(l1,l2,arr1,arr2)
        minheap = [topNode]
        seen.add(topNode)
        for i in range(k):
            topNode = heapq.heappop(minheap)
            res.append(topNode.val1+topNode.val2)
            if topNode.l1>0:
                curNode = Node(topNode.l1-1,topNode.l2,arr1,arr2)
                if curNode not in seen:
                    heapq.heappush(minheap,curNode)
                    seen.add(curNode)
            if topNode.l2>0:
                curNode = Node(topNode.l1,topNode.l2-2,arr1,arr2)
                if curNode not in seen:
                    heapq.heappush(minheap,curNode)
                    seen.add(curNode)
        return res

nums1 = [i for i in range(11)]
nums2 = [i for i in range(11)]
k=10
Solution().topKSum(nums1,nums2,k)

31.正数数组的最小不可组成和

在区间[min,max]上,如果有数不可以被arr某一子集相加得到,那么最小的数即是最小不可组成和

# O(mn)  背包
class Solution():
    def unformedSum(self,nums):
        if not nums:
            return 1
        n = len(nums)
        min_num = min(nums)
        max_num = sum(nums)

        dp = [[False for j in range(max_num+1)]for i in range(n+1)]
#         dp[0][0] = True
        for i in range(n+1):
            dp[i][0] = True
        for i in range(1,n+1):
            for j in range(1,max_num+1):
                if dp[i][j]:
                    continue
                elif j>=nums[i-1]:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i-1]]
                else:
                    dp[i][j] = dp[i-1][j]
        for j in range(min_num,max_num):
            if dp[-1][j] == False:
                return j
        return max_num+1


nums = [4,5]
Solution().unformedSum(nums)

进阶:如果已知正数数组arr中肯定有1这个数,是否能更快地得到最小不可组成和?

# O(nlogn)
class Solution():
    def unformedSum_1(self,nums):
        nums.sort()
        ans = 0
        for n in nums:
            if n > ans+1:
                return ans+1
            ans += n
        return ans+1


nums = [1,3,5]
Solution().unformedSum_1(nums)

32.无限长数字序列

无限长数字序列,1,2,2,3,3,3,4,4,4,4...;n从1开始计数,求第n项为多少 如:输入169;输出18。

import math
class Solution():
    def getRes(self,n):
        return math.ceil(((1+8*n)**0.5-1)/2)   #注意向上取整

n = 1
Solution().getRes(n)

33.最短字符串

给定一个字符串s,请计算输出含有连续两个s作为字串的最短字符串,中间位置可以重复,例如,'ababa'含有两个'aba'

class Solution():
    def getRes(self,s):
        if not s:return s
        if len(s)==1:return s+s
#         if l
        tmp = s + '#'
        next = self.get_next(tmp)
        ind = next[-1]
        return s+s[ind:]

    def get_next(self,p):
        len_p = len(p)
        next = [-1 for i in range(len_p)]
        j,k = 0,-1
        while j<len_p-1:
            if p[j] == p[k] or k==-1:
                j+=1
                k+=1
                next[j] = k
            else:
                k = next[k]
        return next


s = 'arbaarbaarba'
Solution().getRes(s)

34.异或为0的不重叠区间

给出一个数组,问最多有多少不重叠的非空区间,使得每个区间内数字xor都等于0。
关键在于xor满足交换律和结合律,使用动态规划解决。

class Solution():
    def getRes(self,nums):
        if not nums:
            return 0
        #dp[i]代表0...i位置有多少不重叠的非空区间
        dp = [0 for i in range(len(nums))]
        dic = {}   #0...i异或结果(key)最早出现在index(val)
        dic[0] = -1   #注意初始化043
        key = 0
        for i in range(len(nums)):
            key = key ^ nums[i]
            if key in dic:
                pre = dic[key]
                if pre == -1:
                    dp[i] = 1
                else:
                    dp[i] = dp[pre]+1
            else:

                dp[i] = dp[i-1] if i>0 else 0
            dic[key] = i
        return max(dp)

nums = [1,2,3,3,2,1,0]
Solution().getRes(nums)

35.完美洗牌问题

有一个数组a1,a2,a3....an b1,b2,b3,....,bn生成 b1a1b2a2...bnan 时间复杂度O(N),空间复杂度O(1)。
让1 2 3 | 4 5 6 变换成 4 1 5 2 6 3-----可使用坐标连环怼
第一个圈:1 -> 2 -> 4 -> 1;
第二个圈:3 -> 6 -> 5 -> 3;

  • 若一个数组长度满足\(3^k - 1(2,8,26...)\),则驱动点为\(1,3,9,..,3^(k-1)\)
  • 若不满足(30),\(a1,a2...a13,(a14,a15,)b1,b2,...,(b14,b15) -> a1,a2,a3,..,a13,b1,b2...,b13,(a14,a15,b14,b15)\)
# 完美洗牌问题
import math
class Solution():
    def getNextPos(self,n,i):
        return 2*i % n


    def reversesome(self,nums,k):
        n = len(nums)
        start = (3**k-1) //2 + 1
        mid = n//2 + 1
        end = mid+(3**k-1) //2
        nums[start:mid] = nums[start:mid][::-1]
        nums[mid:end] = nums[mid:end][::-1]
        nums[start:end] = nums[start:end][::-1]
        return nums


    def getNew(self,nums,head):
        n = len(nums)
        pos = head
        next_pos = self.getNextPos(n,pos)
        ones = True
        pre_tmp = nums[head]
        while True :
            tmp = nums[next_pos]
            nums[next_pos] = pre_tmp
            if pos == head and not ones:
                break
            pos = next_pos
            next_pos = self.getNextPos(n,next_pos)
            pre_tmp = tmp
            ones = False

        return nums

    def getheads(self,nums,k):
        n = len(nums)
        heads = []
        for i in range(1,k+1):
            heads.append(3**(i-1))
        return heads

    def PerfectShuffle(self,nums):
        if not nums:
            return
        nums.insert(0,0)
        n = len(nums)
        # 若长度满足3^k - 1
        if 3**50 % n == 0:
            k = int(math.log(n,3))
            heads = self.getheads(nums,k)
            for head in heads:
                nums = self.getNew(nums,head)
        # 不满足
        else:
            k = int(math.log(n,3))
            nums = self.reversesome(nums,k)
            index = 3**k
            heads = self.getheads(nums[:index],k)
            for head in heads:
                nums[:index] = self.getNew(nums[:index],head)

            nums[index:] = self.PerfectShuffle(nums[index:])
        del nums[0]
        return nums

# 经典:一个数组无序,调整数组为a<=b>=c<=d>=e<=f>=h
# 排序,调成a1b1a2b2...

# 0 1,2,    3,4
nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
Solution().PerfectShuffle(nums)

36.大数相加(包括负数)

大数相加用一个list即可解决,关键在于负数的加法。联想到计算机内部负数使用补码来表示的,可以使用补码表示负数再进行相加,即可解决。

# 大数相加(包括负数)
class Solution():
    def LongAdd(self, a, b):
        al, bl = list(a), list(b)
        af = bf = False
        if a[0] == '-':
            af = True
            del al[0]
        if b[0] == '-':
            bf = True
            del bl[0]
        maxsize = max(len(al), len(bl))
        la, lb = self.decode(al, af, maxsize), self.decode(bl, bf, maxsize)
        res = self.Add(la, lb)
        if res[0] == 9:
            return self.getbase(res[1:])
        else:
            return res[1:]

    def getbase(self,s):
        res = []
        res += '-'
        tmp = self.LongAdd(''.join(list(map(str,s))), '-1')
        for i in tmp:
            if res == ['-'] and i==9:
                continue
            res.append(9-int(i))

        return res

    def Add(self, a, b):
        res = []
        carry = 0
        for i in reversed(range(len(a))):
            val = carry + a[i] + b[i]
            carry = val // 10
            val = val % 10
            res.append(val)
        return res[::-1]

    def decode(self, s, flag, maxsize):
        res = []
        s = list(map(int, s))
        if flag:
            for i in range(maxsize - len(s)+1):
                res.append(9)
            tmp = []
            for n in s:
                tmp.append(9 - n)
            return res + self.LongAdd(''.join(list(map(str,tmp))), '1')
        else:
            for i in range(maxsize - len(s)+1):
                res.append(0)
            res += s
            return res

a = '-423423432'
b = '-5'
res = Solution().LongAdd(a, b)
print('res,',res )

37.大数相乘

#大数相乘
class Solution():
    def mul(self,n1,n2):
        n1.reverse()
        n2.reverse()
        len1,len2 = len(n1),len(n2)
        n3=[0 for i in range(len1+len2)]

        for i in range(len1):
            for j in range(len2):
                n3[i+j] += n1[i]*n2[j]
        for i in range(len(n3)):
            if(n3[i]>9):
                n3[i+1] += n3[i]//10
                n3[i] = n3[i]%10
        n3.reverse()
        return n3
res = Solution().mul([2,4,5,6,6],[4,5,2,0,5,3])
print('res',res)

38.计算逆序对的距离和

分治解决。关键在于在分治排序的时候,需要代入每个数原来的索引。

n = int(input())
nums = list(map(int, input().split()))

class Solution():
    def countSmallerSum(self, nums):
        if not nums:
            return []
        self.res = 0
        self.func(list(enumerate(nums)), 0, len(nums) - 1)
        return self.res


    def func(self, s, lo, hi):
        if hi <= lo: return
        mid = lo + (hi - lo) // 2
        self.func(s, lo, mid)
        self.func(s, mid + 1, hi)
        self.merge(s, lo, mid, hi)


    def merge(self, a, lo, mid, hi):
        i, j = lo, mid + 1

        aux = a[:]
        for k in range(lo, hi + 1):
            if i > mid:
                a[k] = aux[j]
                j += 1
            elif j > hi:
                for t in range(mid+1,j):
                    self.res += abs(aux[t][0] - aux[i][0])
                a[k] = aux[i]
                i += 1
            elif aux[j][1] < aux[i][1]:
                a[k] = aux[j]
                j += 1
            else:
                for t in range(mid+1,j):
                    self.res += abs(aux[t][0] - aux[i][0])
                a[k] = aux[i]
                i += 1
res = Solution().countSmallerSum(nums)
print(res)

39.矩阵链乘法

参考【动态规划】矩阵链乘法,动态规划之矩阵链乘法,矩阵链乘法问题(matrix-chain multiplication problem)可描述如下:给定n个矩阵的链\(<A1,A2,...,An>\),矩阵\(A_i\)的规模为\(p(i-1)×p(i) (1<=i<=n)\),求完全括号化方案,使得计算乘积\(A1A2...An\)所需标量乘法次数最少。
因为括号方案的数量与n呈指数关系,所以通过暴力搜索穷尽所有可能的括号化方案来寻找最优方案是一个糟糕策略。
假定矩阵Ai的规模为\(p(i-1)×pi(i=1,2,...,n)\)。它的输入是一个序列\(p=<p0,p1,...,pn>\),其长度为\(p.length=n+1\)。过程用一个辅助表\(m[1..n,1..n]\)来保存代价\(m[i,j]\),用另一个辅助表\(s[1..n-1,2..n](s[1,2]..s[n-1,n]这里i<j)\)记录最优值\(m[i,j]\)对应的分割点\(k\)。我们就可以利用表\(s\)构造最优解。
   对于矩阵\(A(i)A(i+1)...A(j)\)最优括号化的子问题,我们认为其规模为链的长度\(j-i+1\)。因为\(j-i+1\)个矩阵链相乘的最优计算代价\(m[i,j]\)只依赖于那么少于\(j-i+1\)个矩阵链相乘的最优计算代价。因此,算法应该按长度递增的顺序求解矩阵链括号化问题,并按对应的顺序填写表\(m\)

p = [30, 35, 15, 5, 10, 20, 25]
def matrix_chain_order(p):
    n = len(p) - 1   # 矩阵个数
    m = [[0 for i in range(n)] for j in range(n)]
    s = [[0 for i in range(n)] for j in range(n)] # 用来记录最优解的括号位置
    for l in range(1, n): # 控制列,从左往右
        for i in range(l-1, -1, -1):  # 控制行,从下往上
            m[i][l] = float('inf') # 保存要填充格子的最优值
            for k in range(i, l):  # 控制分割点
                q = m[i][k] + m[k+1][l] + p[i]*p[k+1]*p[l+1]
                if q < m[i][l]:
                    m[i][l] = q
                    s[i][l] = k
    return m, s

def print_option_parens(s, i, j):
    if i == j:
        print('A'+str(i+1), end='')
    else:
        print('(', end='')
        print_option_parens(s, i, s[i][j])
        print_option_parens(s, s[i][j]+1, j)
        print(')', end='')

r, s = matrix_chain_order(p)
print_option_parens(s, 0, 5)
posted @ 2019-10-21 09:04  Jamest  阅读(678)  评论(0编辑  收藏  举报