面经整理计划——第四弹

算法&&八股文&&其他


一、算法篇

  1. 给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素

分析:middle(lc82)
双指针:首先我们可以设置一个newhead对象,pre = newhead,cur = head,在已知head的情况下,我们开始循环判断cur对象,设置循环条件为cur ->next不为空,判断cur->val是否等于cur->next->val

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param head ListNode类 
# @return ListNode类
#
class Solution:
    def deleteDuplicates(self , head: ListNode) -> ListNode:
        tmpNode = ListNode(-1)      #虚拟头节点
        preNode = tmpNode           #记录当前节点前驱
        cur = head
        preNode.next = cur
        while cur:                  #遍历链表
            if cur.next and cur.val == cur.next.val:     #判断是否重复
                while cur.next and cur.val == cur.next.val:
                    cur = cur.next
                cur = cur.next        #找到第一个与当前节点不相等的节点作为新的当前节点
                preNode.next = cur    #将前驱节点的后继更新为
            else:
                preNode = preNode.next  #若不重复,直接更新前驱与当前节点
                cur = cur.next
        return tmpNode.next
        # write code here
  1. 先/中/后序遍历(迭代和递归两种方式)

分析:简单(nc45)

递归
「二叉树的先序遍历」的思路是:先访问根结点,再访问左子树,最后访问右子树;
「二叉树的中序遍历」的思路是:先访问左子树,再访问根结点,最后访问右子树;
「二叉树的后序遍历」的思路是:先访问左子树,再访问右子树,最后访问根结点;

迭代
先序遍历步骤如下:
1.根结点入栈;
 重复如下操作:
  i.访问栈顶结点
  ii.右孩子入栈(若有)
  iii.左孩子入栈(若有)
ALT
中序遍历过程步骤如下:
i.访问以当前结点p为根结点的「最左孩子」n;
ii.访问n,并将p更新至其右子树上:p = p->right;
iii.若p无右子树,则说明p是某棵子树的根结点,则在下一次循环中访问p;若p有右子树,则按照上述相同步骤访问该右子树,最后访问p。
ALT
后序遍历过程步骤如下:

1. 定义last指针,用以代表「上次访问的位置」,p结点用来遍历;
2. 由于后续遍历需要先访问左子树和右子树、最后访问根结点,因此在满足如下条件时才能访问该结点:
  i. 该结点没有右子树;
  ii. 该结点有右子树,且上次访问的结点(last指针)为其右子树,即该右子树已经被访问过了;

3. 在不满足上述条件的情况下,说明当前结点还不能被访问,故先访问其右子树:p = p->right;
4. 将p结点置为NULL,防止下次while循环时重复访问;

ALT

/**
 * struct TreeNode {
 *  int val;
 *  struct TreeNode *left;
 *  struct TreeNode *right;
 * };
 */
//递归
class Solution_recursion {
public:
    /**
     *
     * @param root TreeNode类 the root of binary tree
     * @return int整型vector<vector<>>
     */
    vector<vector<int> > res;
    vector<int> preorder, inorder, postorder;
    vector<vector<int> > threeOrders(TreeNode* root) {
        preOrder(root);
        inOrder(root);
        postOrder(root);
        res.push_back(preorder);
        res.push_back(inorder);
        res.push_back(postorder);
        return res;
    }
    void preOrder(TreeNode* root) {
        if (!root) return;
        preorder.push_back(root->val); // 访问根结点
        preOrder(root->left); // 访问左子树
        preOrder(root->right); // 访问右子树
    }
    void inOrder(TreeNode* root) {
        if (!root) return;
        inOrder(root->left); // 访问左子树
        inorder.push_back(root->val); // 访问根结点
        inOrder(root->right); // 访问右子树
    }
    void postOrder(TreeNode* root) {
        if (!root) return;
        postOrder(root->left); // 访问左子树
        postOrder(root->right); // 访问右子树
        postorder.push_back(root->val); // 访问根结点
    }
};
//迭代
class Solution_iter {
public:
    /**
     * 
     * @param root TreeNode类 the root of binary tree
     * @return int整型vector<vector<>>
     */
    vector<vector<int> > res; 
    vector<int> preorder, inorder, postorder; 
    vector<vector<int> > threeOrders(TreeNode* root) {
        preOrder(root); 
        inOrder(root); 
        postOrder(root); 
        res.push_back(preorder); 
        res.push_back(inorder); 
        res.push_back(postorder); 
        return res; 
    }
    void preOrder(TreeNode* root) {
        if (!root) return; 
        stack<TreeNode*> s; 
        TreeNode* p = root; 
        s.push(p); 
        while (s.size()) {
            p = s.top(); // 访问根结点
            s.pop(); // 根结点出栈
            preorder.push_back(p->val); 
            if (p->right) s.push(p->right); // 右子树入栈
            if (p->left) s.push(p->left); // 左子树入栈
        }
        return; 
    }
    void inOrder(TreeNode* root) {
        if (!root) return; 
        stack<TreeNode*> s; 
        TreeNode* p = root; 
        while (s.size() || p) {
            while (p) { // 寻找最左的孩子
                s.push(p); 
                p = p->left; 
            }
            p = s.top(); 
            inorder.push_back(p->val); // 访问
            s.pop(); 
            p = p->right; // 右子树
        }
        return; 
    }
    void postOrder(TreeNode* root) {
        if (!root) return; 
        stack<TreeNode*> s; 
        TreeNode* p = root, *last = NULL; // last记录先前被访问的结点
        while (s.size() || p) {
            while (p) { // 最左的孩子
                s.push(p); 
                p = p->left; 
            }
            p = s.top(); 
            if (!p->right || last == p->right) { // 若该结点没有右孩子,或上一次访问的是右子树,则直接访问该结点
                s.pop(); 
                postorder.push_back(p->val); 
                last = p; // 更新last
                p = NULL; // p置空,防止下一次循环重复访问
            } else {
                p = p->right; // 右子树
            }
        }
    }
};
  1. 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

分析:简单(nc71)
二分
算法流程:

1. 初始化: 声明 i, j 双指针分别指向 array 数组左右两端
2. 循环二分: 设 m = (i + j) / 2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i≤m)
  i. 当 array[m] > array[j] 时: m 一定在 左排序数组 中,即旋转点 x 一定在 [m + 1, j] 闭区间内,因此执行 i = m + 1
  ii. 当 array[m] < array[j] 时: m 一定在 右排序数组 中,即旋转点 x 一定在[i, m]闭区间内,因此执行 j = m
  iii. 当 array[m] = array[j] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i, m] 还是 [m + 1, j] 区间中。解决方案: 执行 j = j - 1 缩小判断范围
3. 返回值: 当 i = j 时跳出二分循环,并返回 旋转点的值 array[i] 即可。

ALT

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        # 特殊情况判断
        if len(rotateArray) == 0:
            return 0
        # 左右指针
        i, j = 0, len(rotateArray) - 1
        while i < j:
            # 确定中点索引
            m = (i + j) // 2
            # m在左排序数组中,旋转点在 [m+1, j] 中
            if rotateArray[m] > rotateArray[j]: i = m + 1
            # m在右排序数组中,旋转点在 [i, m] 中
            elif rotateArray[m] < rotateArray[j]: j = m
            else: j -= 1
        # 返回结果
        return rotateArray[i]
  1. 给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。

分析:middle(nc42)
三种方法:
回溯——基于辅助数组
回溯——基于交换
基于c++库函数next_permutation

#include <vector>
#include <algorithm>
using namespace std;
//法一
class Solution {
public:
    vector<vector<int> > permuteUnique(vector<int> &num) {
        vector<vector<int> > res;
        //visited数组在标记当前元素是否已经被选择过了,从而避免得到重复的排列
        vector<int> visited(num.size(), 0);
        //因为存在重复项,为了更好的处理,将数组先排序
        sort(num.begin(), num.end());
        dfs(res, num, visited, vector<int>());
        return res;
    }

    void dfs(vector<vector<int> > &res, vector<int> &num, vector<int> &visited, vector<int> vec) {
        if (vec.size() == num.size()) { res.push_back(vec); return; }
        for (int i = 0; i < num.size(); ++i) {
            if (visited[i] == 1) continue;
            //举例 112
            if (i>0 && num[i-1]==num[i] && !visited[i-1]) continue;
            visited[i] = 1;
            vec.push_back(num[i]);
            dfs(res, num, visited, vec);
            vec.pop_back();
            visited[i] = 0;
        }
    }
};

//法二
class Solution {
public:
    vector<vector<int> > permuteUnique(vector<int> &num) {
        vector<vector<int> > res;
        set<vector<int>> st;
        dfs(st, num, 0);
        for (auto vec : st) res.push_back(vec);
        return res;
    }

    void dfs(set<vector<int> > &st, vector<int> &num, int idx) {
        if (idx == num.size()-1) { st.insert(num); return; }
        for (int i = idx; i < num.size(); ++i) {
            if (i != idx && num[i] == num[idx]) continue; // 剪枝
            swap(num[i], num[idx]);
            dfs(st, num, idx+1);
            swap(num[i], num[idx]);
        }
    }
};

//法三
class Solution {
public:
    vector<vector<int> > permuteUnique(vector<int> &num) {
        vector<vector<int> > res;
        sort(num.begin(), num.end());
        do{
            res.push_back(num);
        } while (next_permutation(num.begin(), num.end()));
        return res;
    }
};
  1. 给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。

分析:middle(nc285)
使用一个二维数组dp,dp[i][j]表示的是从左上角到坐标(i,j)的最小路径和。那么走到坐标(i,j)的位置只有这两种可能,要么从上面(i-1,j)走下来,要么从左边(i,j-1)走过来,我们要选择路径和最小的再加上当前坐标的值就是到坐标
(i,j)的最小路径。

# @param matrix int整型二维数组 the matrix
# @return int整型
#
class Solution:
    def minPathSum(self , matrix ):
        n = len(matrix)
        m = len(matrix[0])
        for r in range(1, n):     #直接在原数组上更新
            matrix[r][0] = matrix[r-1][0] + matrix[r][0]
        for c in range(1, m):
            matrix[0][c] = matrix[0][c-1] + matrix[0][c]
        for r in range(1, n):
            for c in range(1, m):
                matrix[r][c] = min(matrix[r-1][c], matrix[r][c-1]) + matrix[r][c]
        return matrix[-1][-1]
        # write code here

二、八股文

  1. ID3,C4.5,CART决策树 (1, 2)
  2. 感受野计算(1, 2)
  3. 矩阵分解有哪几种(1, 2, 3, 4)
  4. c++数组和vector容器的区别(1, 2, 3)
  5. BNNeck介绍(1)
  6. kdTree算法和KNN算法(1, 2, 3, 4)
  7. 注意力机制的算法复杂度(1)
  8. ADAM和ADAMW优化器(1, 2)
  9. BERT的激活函数GELU(1, 2)

三、其他

  1. t-SNE降维原理(1)
  2. Python 继承object类和不继承的区别(1)

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

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

  2. 给定一个数字矩阵和一个数字target,比如5。从数字1开始(矩阵中可能有多个1),每次可以向上下左右选择一个方向移动一次,可以移动的条件是下个数字必须是上个数字+1,比如1必须找上下左右为2的点,2必须找上下左右为3的点,以此类推。求到达target一共有几个路径。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值