面经整理计划——第二弹

算法&&八股文&&其他


一、算法篇

  1. 给定彼此独立的两棵二叉树,树上的节点值两两不同,判断 t1 树是否有与 t2 树完全相同的子树。
    子树指一棵树的某个节点的全部后继节点
    数据范围:树的节点数满足 0 <n≤500000

分析:简单题(lc572)
递归 解法:深度优先搜索枚举 s 中的每一个节点,判断这个点的子树是否和 t 相等。如何判断一个节点的子树是否和 t 相等呢,我们又需要做一次深度优先搜索来检查,即让两个指针一开始先指向该节点和 t 的根,然后「同步移动」两根指针来「同步遍历」这两棵树,判断对应位置是否相等。
先序 遍历:将t1和t2通过先序遍历序列化成为一个字符串,然后再判断t1序列化后的字符串是否包含t2序列化后的字符串。
需要注意的点是:当前节点的左右节点要是为空的时候,需要加入占位符表示为空。

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

#
# 
# @param root1 TreeNode类 
# @param root2 TreeNode类 
# @return bool布尔型
#
class Solution_dfs:
    def isContains(self , root1 , root2 ):
        # write code here
        if not root1 and not root2:
            return True
        if not root1 or not root2:
            return False
        ans1,ans2=False,False
        if root1.val==root2.val:
            ans1=self.isContains(root1.left, root2.left) and self.isContains(root1.right, root2.right)
        ans2=self.isContains(root1.left, root2) or self.isContains(root1.right, root2)
        return ans1 or ans2
#只可使用先序遍历,而非中/后序遍历
class Solution_preorder:
    def isContains(self , root1 , root2 ):
        if not root1 and not root2:
            return True
        if not root1 or not root2:
            return False
        li_1 = []
        li_2 = []
        preorder(root1,li_1)
        preorder(root2,li_2)
        str1 = ''.join(li_1)
        str2 = ''.join(li_2)
        return str2 in str1
def preorder(root,li):
    if root:
        li.append(str(root.val))
        preorder(root.left,li)
        preorder(root.right,li)
    else:
        li.append('-')
        # write code here
  1. 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素

分析:middle(lc347)
利用堆的思想 :建立一个 小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。
如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。

基于快速排序的方法,求出「出现次数数组」的前 k 大的值。
将数组用快排从大到小排序,取数组的第一个数a[start]为pivot,那么经过一轮调整之后,数组左边的所有值大于或等于temp,数组右边的所有值都小于或等于temp,假设此时temp是数组第i个数。
如果i正好等于K,那么temp就是第K大值
如果i大于K,那么说明第K大值在数组左边,则继续在左边查找
如果i小于K,那么说明第K大值在数组的右边,继续在右边查找

class Solution_minHeap:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        count = collections.Counter(nums)
        heap = []
        for key, val in count.items():
            if len(heap) >= k:
                if val > heap[0][0]:
                    heapq.heapreplace(heap, (val, key))
            else:
                heapq.heappush(heap, (val, key))
        return [item[1] for item in heap]
        
class Solution_quickSort:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        count = collections.Counter(nums)
        num_cnt = list(count.items())
        topKs = self.findTopK(num_cnt, k, 0, len(num_cnt) - 1)
        return [item[0] for item in topKs]
    
    def findTopK(self, num_cnt, k, low, high):
        pivot = random.randint(low, high)
        num_cnt[low], num_cnt[pivot] = num_cnt[pivot], num_cnt[low]
        base = num_cnt[low][1]
        i = low
        for j in range(low + 1, high + 1):
            if num_cnt[j][1] > base:
                num_cnt[i + 1], num_cnt[j] = num_cnt[j], num_cnt[i + 1]
                i += 1
        num_cnt[low], num_cnt[i] = num_cnt[i], num_cnt[low]
        if i == k - 1:
            return num_cnt[:k]
        elif i > k - 1:
            return self.findTopK(num_cnt, k, low, i - 1)
        else:
            return self.findTopK(num_cnt, k, i + 1, high)
  1. 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
    求在该柱状图中,能够勾勒出来的矩形的最大面积。
    ALT

分析:hard(lc84)
第 i 位置最大面积:以i 为中心,向左找第一个小于 heights[i] 的位置 lefti;向右找第一个小于 heights[i] 的位置 righti,即最大面积为 heights[i] * (righti - lefti -1)

思路1:维护一个 单调递增的栈,就可以找到 lefti 和 righti

思路2:当我们找 i 左边第一个小于 heights[i] 如果 heights[i-1] >= heights[i] 其实就是和 heights[i-1] 左边第一个小于 heights[i-1] 一样。依次类推,右边同理

class Solution_monoStack:
    def largestRectangleArea(self, heights: List[int]) -> int:
        stack = []
        heights = [0] + heights + [0]  #左右添加两个虚拟哨兵, 方便处理
        res = 0
        for i in range(len(heights)):
            while stack and heights[stack[-1]] > heights[i]:
                tmp = stack.pop()
                res = max(res, (i - stack[-1] - 1) * heights[tmp])
            stack.append(i)
        return res
        
class Solution_dp:
    def largestRectangleArea(self, heights: List[int]) -> int:
        if not heights:
            return 0
        n = len(heights)
        left_i = [0] * n
        right_i = [0] * n
        left_i[0] = -1
        right_i[-1] = n
        for i in range(1, n):
            tmp = i - 1
            while tmp >= 0 and heights[tmp] >= heights[i]:
                tmp = left_i[tmp]
            left_i[i] = tmp
        for i in range(n - 2, -1, -1):
            tmp = i + 1
            while tmp < n and heights[tmp] >= heights[i]:
                tmp = right_i[tmp]
            right_i[i] = tmp
        res = 0
        for i in range(n):
            res = max(res, (right_i[i] - left_i[i] - 1) * heights[i])
        return res
  1. 给定一个长度为n的数组arr,返回arr的最长无重复元素子数组
#双指针
class Solution:
    def maxLength(self , arr ):
        if len(arr) == 0:
            return 0
        d = {}
        startpos = 0
        endpos = 0
        ans = []
        while endpos <= len(arr) - 1:
            if arr[endpos] in d: 
            startpos =max(startpos,d[arr[endpos]]+1)    #若出现与d中重复元素,判断重复元素位置与startpos的大小,在startpos前则不管,否则更新startpos
            if endpos - startpos + 1 > len(ans): ans = arr[startpos:endpos+1]               #更新当前最大长度
            d[arr[endpos]] = endpos            #记录扫描过的元素,值-索引
            endpos += 1
        return ans
#队列
class betterSolution:
    def maxLength(self , arr: List[int]) -> int:
        queue = []
        ans = []
        for num in arr:
            while num in queue: queue.pop(0)
            queue.append(num)
            if len(queue) > len(ans): ans = queue
        return ans

  1. 给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的路径的数目。
    路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)

分析:middle(lc437)
我们定义节点的前缀和为:由根结点到当前结点的路径上所有节点的和。我们利用先序遍历二叉树,记录下根节点 root 到当前节点 p 的路径上除当前节点以外所有节点的前缀和,在已保存的路径前缀和中查找是否存在前缀和刚好等于当前节点到根节点的前缀和 curr 减去 targetSum

class Solution:
    def pathSum(self, root: TreeNode, targetSum: int) -> int:
        prefix = collections.defaultdict(int)
        prefix[0] = 1

        def dfs(root, curr):
            if not root:
                return 0
            
            ret = 0
            curr += root.val
            ret += prefix[curr - targetSum]
            prefix[curr] += 1
            ret += dfs(root.left, curr)
            ret += dfs(root.right, curr)
            prefix[curr] -= 1

            return ret

        return dfs(root, 0)
  1. 对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度

分析:middle(nc17)

思路一——中心扩散法:我们遍历字符串的每一个字符,然后以当前字符为中心往两边扩散,查找最长的回文子串

思路二——动态规划:定义二维数组dp[length][length],如果dp[left][right]为true,则表示字符串从left到right是回文子串,如果dp[left][right]为false,则表示字符串从left到right不是回文子串。
如果dp[left+1][right-1]为true,我们判断s[left]和s[right]是否相等,如果相等,那么dp[left][right]肯定也是回文子串,否则dp[left][right]一定不是回文子串

class Solution:
    def getLongestPalindrome(self , A: str) -> int:
        n = len(A)
        temp = 0
        for i in range(n):
            temp = max(temp,spread(A,i, i, n),spread(A,i, i+1, n))  #注意奇长度回文串与偶长度回文串的参数略有不同
        return temp
def spread(A,l,r,n):        #以A[left]和A[right]为中心向左右两边扩散,返回扩散的最大长度
    while l >= 0 and r <= n-1 and A[l] == A[r]:       #利用回文子串正反一样的特点进行扩散
        l = l - 1
        r = r + 1
    return r-l-1
        # write code here
class Solution {
public:
    int getLongestPalindrome(string A, int n) {
        if(n <= 1) return n;
        int longest = 1;
        bool dp[n][n];
        for(int i = 0; i < n; i++) { //从短到长对每种长度分别判断,可以这么做是因为判断较长的需要利用较短的
            for(int j = 0; j< n - i; j++) { //从头开始对长度i+1的子字符串判断
                if(i == 0) dp[j][j] = 1; //长度1一定为回文
                else if(i == 1) dp[j][j + 1] = (A[j] == A[j + 1]); //长度2判断头尾是否相等
                else if(A[j] == A[j + i]) dp[j][j + i] = dp[j + 1][j + i - 1]; //长度大于等于3,判断两头是否相等,若相等则同去两头的bool值一样
                else dp[j][j + i] = 0; //否则为0
                if(dp[j][j + i]) longest = max(longest, i + 1); //更新最大值
            }
        }
        return longest;
    }
};
  1. k的 n 次方的最后三位数

分析
乘积的最后三位的值只与乘数和被乘数的后三位有关,与乘数和被乘数的高位无关

def theLastThreeNumber(base,N):
	ans = 1
	for i in range(N):
		ans = (ans * base) % 1000
	return ans
  1. 给定数组 arr ,设长度为 n ,输出 arr 的最长严格上升子序列。(如果有多个答案,请输出其中 按数值进行比较的字典序最小的那个)

分析:middle(nc91)
两步走:
第一步——求最长递增子序列长度
第二步——求字典序靠前的子序列

对于第一步:贪心+二分,时间复杂度为O(nlogn)
(动态规划,时间复杂度为O(n^2),会超
对于第二步,假设我们原始数组是arr1,得到的maxLen为[1,2,3,1,3],最终输出结果为res(字典序最小的最长递增子序列),res的最后一个元素在arr1中位置无庸置疑是maxLen[i]==3对应的下标,那么到底是arr1[2]还是arr1[4]呢?如果是arr1[2],那么arr1[2]<arr1[4],则maxLen[4]==4,与已知条件相悖。因此我们应该取arr1[4]放在res的最后一个位置

#
# retrun the longest increasing subsequence
# @param arr int整型一维数组 the array
# @return int整型一维数组
#
class Solution:
    def LIS(self , arr ):
        if not arr: return 0
        monoList = [arr[0]]      #长度为 i+1 的子序列的最后一位的最小值(不是解,只是长度关联),单调递增
        maxlen = [1]       #maxlen[i]记录以arr[i]结尾的最长递增子列长度
        longestLenth = 1			   #记录最长递增子序列长度
        for i in arr[1:]:
            if i > monoList[-1]:
                monoList.append(i)
                longestLenth = len(monoList)
                maxlen.append(len(monoList))
            else:
                pos = findPosition(monoList, i)  #二分查找恰好合适的位置
                monoList[pos] = i	#对该位置数字进行更新
                maxlen.append(pos+1)
        length = len(monoList)
        ans = [0]*length
        for j in range(-1,-len(arr)-1,-1): #倒着遍历arr,找到满足长度的maxlen就记录,然后更新(即同样值的maxlen,选尽量靠右边的)
            if length > 0:
                if maxlen[j] == length:
                    ans[length-1] = arr[j]
                    length -= 1
            else:break
        return ans, longestLenth
def findPosition(li, t):
    if li[-1] < t: return None
    l = 0
    r = len(li) - 1
    while l < r:
        mid = (l + r) >> 1
        if li[mid] >= t:
            r = mid
        else:
            l = mid + 1
    return r
        # write code here
  1. 路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径至少包含一个 节点,且不一定经过根节点。
    路径和是路径中各节点值的总和。
    给你一个二叉树的根节点 root ,返回其 最大路径和 。

分析:hard(lc124)
首先,考虑实现一个简化的函数 maxGain(node),该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大。具体而言,该函数的计算如下:
空节点的最大贡献值等于 0。
非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)
根据函数 maxGain 得到每个节点的最大贡献值之后,计算二叉树的最大路径和:对于二叉树中的一个节点,该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和。维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
# @param root TreeNode类 
# @return int整型
#
class Solution:
    def maxPathSum(self , root ):
        res = [-float('inf')   #此处使用列表可变类型,可起到全局变量的作用
        _ = maxGain(root, res)
        return res[0]
def maxGain(root,a):
    if not root: return 0
    #递归计算左右子节点的最大贡献值
    #只有在最大贡献值大于 0 时,才会选取对应子节点
    leftG, rightG = maxGain(root.left,a), maxGain(root.right,a)
    #节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
    a[0] = max(a[0], root.val + max(leftG,0) + max(rightG, 0))
    #返回节点的最大贡献值
    gain = max(leftG, rightG, 0) + root.val
    return gain		#注意此处并不是返回a[0](最大路径和)
        # write code here
  1. 一群孩子做游戏,现在请你根据游戏得分来发糖果,要求如下:1. 每个孩子不管得分多少,起码分到一个糖果;2. 任意两个相邻的孩子之间,得分较多的孩子必须拿多一些糖果。(若相同则无此限制)
    给定一个数组 arrarr 代表得分数组,请返回最少需要多少糖果。

分析:middle(lc135)
贪心 :左右各遍历一次
把所有孩子的糖果数初始化为 1;
先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的 糖果数加 1;
再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数;
则左边孩子的糖果数更新为右边孩子的糖果数加 1

#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# pick candy
# @param arr int整型一维数组 the array
# @return int整型
#注意到第i个孩子所需的最少糖果数由以下两者较大者决定:
#以i结尾的最长严格递增得分序列长度; 
#以i开头的最长严格递减得分序列长度决定,可递推求
class Solution:
    def candy(self , arr: List[int]) -> int:
        lenth = len(arr)
        afterless = [1] * lenth
        for i in range(lenth-2, -1, -1):
            if arr[i] > arr[i+1]:
                afterless[i] = afterless[i+1] + 1
        frontless = [1] * lenth
        for i in range(1, lenth):
            if arr[i] > arr[i-1]:
                frontless[i] = frontless[i-1] + 1
        res = 0
        for i in range(lenth):
            if max(afterless[i], frontless[i]) == 1:
            	#此时i+1,i-1位置都至少大于等于i位置
                res += 1             #知i位置给1个糖果不影响其他位置
            else:
                #得分趋势为/*\ *代表第i个位置
                res += max(afterless[i], frontless[i])
        return res
        # write code here
  1. N 皇后问题是指在 n * n 的棋盘上要摆 n 个皇后,
    要求:任何两个皇后不同行,不同列也不在同一条斜线(与主/副对角线平行)上
    求给一个整数 n ,返回 n 皇后的摆法数。

分析:hard(lc51)
基于集合的回溯
1.设置三个集合分别记录不能再被选中的的列col,正斜线pos,反斜线neg
2.规律:行号i - 列号j 可确定唯一正斜线,行号i + 列号j 可确定唯一反斜线
3.符合要求的点记录当前点并递归下一个皇后,最后一个皇后成功安置后将res+1,然后需回溯回初始点将初始点删除,将初始点的皇后放置其他位置进行判断
4.不符合要求的需要进行循环

# @param n int整型 the n
# @return int整型
#
class Solution: 
    def Nqueen(self , n ):
        # write code here
        col=set()
        dia1=set()
        dia2=set()
        self.ans=0
        def dfs(num,col,dia1,dia2):
            if num==n:
                self.ans+=1
            else:
                for j in range(n):
                    if j in col or num-j in dia1 or j+num in dia2:
                        continue
                    col.add(j)
                    dia1.add(num-j)
                    dia2.add(j+num)
                    dfs(num+1,col,dia1,dia2)
                    col.remove(j)
                    dia1.remove(num-j)
                    dia2.remove(j+num)
        dfs(0,col,dia1,dia2)
        return self.ans

二、八股文

  1. 进程和线程的区别 (1, 2, 3, 4, 5)
  2. NMS和soft-nms算法(1,  2)
  3. python迭代器和生成器(1, 2, 3)
  4. GBDT为什么拟合负梯度而不是残差(1)
  5. attention为什么要除以根号下dk(1)

三、其他


待解决 (欢迎评论区或私信解答)

  1. 给出一个有序数组A和一个常数C,求所有长度为C的子序列中的最大的间距D。
    一个数组的间距的定义:所有相邻两个元素中,后一个元素减去前一个元素的差值的最小值. 比如[1,4,6,9]的间距是2.
    例子:A:[1,3,6,10], C:3。最大间距D应该是4,对应的一个子序列可以是[1,6,10]。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值