码迷,mamicode.com
首页 > 其他好文 > 详细

[总结]一些经典问题

时间:2019-10-21 09:47:53      阅读:96      评论:0      收藏:0      [点我收藏+]

标签:first   hnu   fast   rom   n+1   list   inline   star   比较   

在查找刷题攻略的时候,也遇到了一些比较经典、有趣的题目,记录在这里,不断更新。难度保持在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\)

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

[总结]一些经典问题

标签:first   hnu   fast   rom   n+1   list   inline   star   比较   

原文地址:https://www.cnblogs.com/hellojamest/p/11711509.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!