剑指offer【数学】

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

  • 哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N)
  • 数组排序法: 将数组 nums 排序,数组中点的元素 一定为众数。
  • 摩尔投票法: 核心理念为票数正负抵消。此方法时间和空间复杂度分别为 O(N)和 O(1),(遍历一边,只有两个变量m和i)为本题的最佳解法。

摩尔投票法

初始化元素m并给计数器i赋初值i = 0
对于输入队列中每一个元素x:
若i = 0, 那么 m = x and i = 1
否则若m = x, 那么 i = i + 1
否则 i = i − 1
返回 m
即便输入序列没有多数元素,这一算法也会返回一个序列元素。然而如果能够进行第二轮遍历,检验返回元素的出现次数,就能判断返回元素是否为多数元素。因此算法需要两次遍历,亚线性空间算法无法通过一次遍历就得出输入中是否存在多数元素。

推论一: 若记 众数 的票数为 +1,非众数 的票数为 −1,则一定有所有数字的 票数和 >0 。
推论二: (设众数为x,共n个数) 若数组的前 a 个数字的 票数和 =0,则 数组剩余 (n−a) 个数字的 票数和一定仍 >0 ,即后 (n−a) 个数字的 众数仍为 x

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # 基准数m, 计数器i
        m,i = nums[0], 0
        for num in nums:

            if i == 0: 
                m,i = num, 0
            i += 1 if num == m else  -1
            
            # if num == m:
            #     i += 1
            # else:
            #     i -= 1
        return m

构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

类似于动态规划的解法

  • 其中两个三角都可以看成一个dp问题,
  • 对于下半部分(绿三角)也就是b[n] = b[n-1]*a[n-1]; --> 同dp的方法
  • 对于上半部分(红三角) b[n] = b[n+1] * a[n+1]; --> 建立一个变量,从下至上的累乘上去即可(注意从倒数第二行开始,遍历到第一行)
class Solution:
    def constructArr(self, a: List[int]) -> List[int]:
        # 初始化b,简历对应长度,每个元素都为1的list
        b = [1]*len(a)
        right_part = 1 # 从下向上的累乘逻辑

        for i in range(1,len(a)):
            b[i] = b[i-1]*a[i-1] # 下三角dp(绿)

        # 右上三角是通过从下到上的累乘逻辑,每次多乘上一个 a[i+1]
        # 注意最后一行是没有右上三角形的,也就是遍历需要从倒数第二行到第一行
        for i in range(len(a)-2,-1,-1):
            right_part *= a[i+1]
            b[i] = b[i] * right_part
        return b

和为S的连续序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

数学法

当前序列的平均值元素个数 = s*

  • 确定左右边界 i,j
  • s = (i+j)*(j-i+1)/2 <-- 高斯求和
  • s = (j^2 - i^2 + j + i)/2


  • 通过遍历i,求出所有j,如果j为整数 j == int(j) 即返回当前list
class Solution:
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        i,j = 1,2
        result = []

        while j>i:
            # 公式、根据i求j
            j = (-1 + (1 + 4 * (2 * target + i * i - i)) ** 0.5) / 2
            if i

滑动窗口(双指针)

左边界 i 和右边界 j
判断i,j之间元素和和target的关系,如果大于target,j左移; 如果小于target,i右移

  • 初始化 i,j = 1,2
  • 循环,while j>i:
  • 如果当前sum>target:需要把当前的最小数剃掉 s-=i,左边界+1,框入更少的元素
  • 如果当前sum
class Solution:
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        i,j = 1,2
        s = 3
        result = []

        while j>i:
            if s == target:
                result.append(list(range(i,j+1)))
            if s >= target:
                s -= i
                i +=1
            elif s < target:
                j += 1
                s += j
                
        return result

圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

  • 假设第一次删除的是第m%n个元素,那么剩下n-1的长度,通过F(n-1)可知如果从头做一遍,剩下第几个
  • 由于题意,在第一次删除第m%n时,指针停留在那里并且继续向后遍历了;而f(n-1)是从头做的结果,所以在得知F(n-1)之后还要再加上这个第一次指针移动的距离,避免越界再用一次%
    f(n, m) = (m % n + f(n-1,m) ) % n = (m + f(n-1,m) ) % n
  • 因此递归的计算 f(n,m), f(n-1,m), f(n-2,m),....
  • stop case: f(1, m): 当序列长度为 1 时,一定会留下唯一的那个元素,它的编号为 0
class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        x = 0
        for i in range(2, n + 1):
            x = (x + m) % i
        return x

你可能感兴趣的:(剑指offer【数学】)