网易 2016 实习研发工程师 3道 编程题

本文探讨了通过构建有向图解决钻石重量比较问题的方法,包括递归搜索和避免重复数据;介绍了二叉树中寻找最远叶节点间距离的策略;并讨论了快速排序与最小堆在寻找第K大数问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 比较重量

给定两颗钻石的编号g1,g2,编号从1开始,同时给定关系数组vector,其中元素为一些二元组,第一个元素为一次比较中较重的钻石的编号,第二个元素为较轻的钻石的编号。最后给定之前的比较次数n。请返回这两颗钻石的关系,若g1更重返回1,g2更重返回-1,无法判断返回0。输入数据保证合法,不会有矛盾情况出现。

测试样例:
2,3,[[1,2],[2,4],[1,3],[4,3]],4
返回: 1

class Cmp {
public:
    int cmp(int g1, int g2, vector<vector<int> > records, int n) {
        // write code here
    }
};

思路:有向图,给定起点和重点,求是否存在路径

1.1 第1次提交 错误 结果超时

题目说没有违例数据,所以没有考虑重复数据和矛盾数据,暴力搜

class Cmp {
public:
    int cmp(int g1, int g2, vector<vector<int> > records, int n) {
        // write code here
        // 转换为邻接表
        vector<int> tempVector;
        vector< vector<int> > graph;
        graph.resize(n+1, tempVector);
        for (int i = 0; i < records.size(); i++) {
            int u = records[i][0];
            int v = records[i][1];
            graph[u].push_back(v);
        }

        int curStart = g1;

        for(int i = 0; i <graph[curStart].size(); i++){
            if(graph[curStart][i] == g2) // 直接相连的情况
                return 1; // return
            else {
                curStart = graph[curStart][i]; // 下个要访问的邻接点
                i = -1; // 继续循环,重新计数
            }
        }
        return -1; // return
    }
};

1.2 第2次提交 错误 递归过深

依然没考虑重复数据和矛盾数据,深度优先搜索,错误提示发生段错误,可能是数组越界,堆栈溢出,递归调用层过多,但是后来发现是ret的返回值判断错了,坑

int dfs(vector<vector<int> >& graph, int start, int end){
    if(start == end) return 1;
    for(int i = 0; i < graph[start].size(); ++i){
        int ret = dfs(graph, graph[start][i], end);
        if(ret){ // 这里判断条件应该改为ret == 1
            return 1;
        }
    }
    return -1;
}

int cmp(int g1, int g2, vector<vector<int> > records, int n) {
    // write code here
    // 转换为邻接表
    vector<int> tempVector;
    vector< vector<int> > graph;
    graph.resize(n+1, tempVector);
    for (int i = 0; i < records.size(); i++) {
        int u = records[i][0];
        int v = records[i][1];
        graph[u].push_back(v);
    }
    int ret = dfs(graph, g1, g2);
    return ret;

}

1.3 最终提交 AC

中间直接提交“return 1”的错误代码时,系统给出提示,测试用例有重复数据例如[5,8][5,8],也有不可能数据例如[5,5],除此之外还要考虑无法判定的情况,这类情况包含是否有环路情况例如1<2<3<1等,还包含双向均不连通的情形。
最终提交的版本:


// 有向图,给定起点终点,求是否存在路径
#include <vector>
#include <map>
using namespace std;

vector<bool> mark; // 标记start节点是否已经有链表,有则直接push,否则申请vector再push
class Cmp{
public:
    // 递归找start到end是否存在路径
    int dfs(map<int, vector<int> >& graph, int start, int end){
        if(start == end) return 1;
        for(int i = 0; i < graph[start].size(); ++i){
            int ret = dfs(graph, graph[start][i], end);
            if(ret == 1){
                return 1;
            }
        }
        return -1;
    }

    // 输入有重复,例如[5,8].....[5,8]重复压入造成递归规模增长
    // 压入前判断当前start顶点的数组中是否已有,是则去掉,否则压入
    bool isExisted(vector<int>& v, int value){
        for(int i =0; i < v.size(); ++i){
            if(value == v[i])
                return true;
        }
        return false;
    }
    int cmp(int g1, int g2, vector<vector<int> > records, int n) {
        // write code here
        // 转换为邻接表
        map<int, vector<int> > graph; // 编号不连续,节省内存
        mark.resize(n+1, false); // mark初始化为false
        for (int i = 0; i < records.size(); i++) {
            int u = records[i][0];
            int v = records[i][1];
            // 去掉矛盾数据,例如[5,5]
            if(u == v) continue;
            if(!mark[u]){ // 当前start点没链表,也肯定不存在重复数据
                mark[u] = true;
                vector<int> tempVec;
                tempVec.push_back(v);
                graph[u] = tempVec;
            } else { // 当前start有链表,继续判断是否有重复数据
                if(!isExisted(graph[u], v)){
                    graph[u].push_back(v);
                }
            }
        }
        // 无法判定的情况,包含了环路的矛盾情况
        int ret1 = dfs(graph, g1, g2);
        int ret2 = dfs(graph, g2, g1);
        if(ret1 == ret2) {return 0;}
        return ret1;
    }
};

附: 前两次提交时用的测试程序

#include <vector>
#include <iostream>

using namespace std;
/*
// 2. 发生段错误,可能是数组越界,堆栈溢出(递归调用层数太多)
int dfs(vector< vector<int> >& graph, int start, int end){
    if(start == end) return 1;
    for(int i = 0; i < graph[start].size(); ++i){
        int ret = dfs(graph, graph[start][i], end);
        if(ret){ //其实是错在这里了,判断条件应该改为ret == 1
            return 1;
        }
    }
    return -1;
}
*/

// 3. 改进的dfs

int main() {
    vector< vector<int> > records;
    vector<int> tempVector;
    vector< vector<int> > graph;
    int n = 4;
    int start = 2;
    int end = 3;
    // 原始输入
    tempVector.push_back(1);
    tempVector.push_back(2);
    records.push_back(tempVector);

    tempVector.clear();
    tempVector.push_back(2);
    tempVector.push_back(4);
    records.push_back(tempVector);

    tempVector.clear();
    tempVector.push_back(1);
    tempVector.push_back(3);
    records.push_back(tempVector);

    tempVector.clear();
    tempVector.push_back(4);
    tempVector.push_back(3);
    records.push_back(tempVector);

    /*
    // for test : print original input
    for (int u = 0; u < records.size(); u++) {
        for (int v = 0; v < records[u].size(); v++) {
            cout << records[u][v] << " ";
        }
        cout << endl;
    }
    */

    // 转换为邻接表
    tempVector.clear();
    graph.resize(n+1, tempVector);
    for (int i = 0; i < records.size(); i++) {
        int u = records[i][0];
        int v = records[i][1];
        graph[u].push_back(v);
    }

    /*
    // for test : print used input
    for (int u = 0; u < graph.size(); u++) {
        for (int v = 0; v < graph[u].size(); v++) {
            cout << graph[u][v] << " ";
        }
        cout << endl;
    }
    */



    // 1. 超时
    /*
    int curStart = start;

    for(int i = 0; i < graph[curStart].size(); i++){
        if(graph[curStart][i] == end) // 直接相连的情况
        {
            cout << 1; // return
            break;
        }
        else {
            curStart = graph[curStart][i]; // 下个要访问的邻接点
            i = -1; // 继续循环,重新计数
        }
    }
    cout << -1; // return
    */

    // 2.调用
    int g1 = start;
    int g2 = end;

    int ret = dfs(graph, g1, g2);
    cout << ret;
    return 0;
}

2 二叉树

有一棵二叉树,树上每个点标有权值,权值各不相同,请设计一个算法算出权值最大的叶节点到权值最小的叶节点的距离。二叉树每条边的距离为1,一个节点经过多少条边到达另一个节点为这两个节点之间的距离。
给定二叉树的根节点root,请返回所求距离。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Tree {
public:
    int getDis(TreeNode* root) {
        // write code here
    }
};

思路:先求最大、最小的叶节点,然后找两个节点的最低公共祖先(LCA),然后求两个节点到LCA的距离和

2.1 第1次提交 错误

第1次提交的时候脑残了,判断叶子节点的条件写错了,叶子节点应该是无左孩也无右孩,所以要在当前层判断,然后继续递归。

2.2 最终提交 AC

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
        val(x), left(NULL), right(NULL) {
    }
};*/
#include <climits>
#include <iostream>
using namespace std;

int current_min;
int current_max;

class Tree {
public:
    // 中序遍历找最小、最大叶子节点
    void findMinMax(TreeNode* root){
        if(root == NULL) return;

        if(NULL == root->left && NULL == root->right){
            if(root->val < current_min) current_min = root->val;
            if(root->val > current_max) current_max = root->val;
        }

        findMinMax(root->left);
        findMinMax(root->right);
    }

    // LCA
    TreeNode* findLCA(TreeNode* root, int min, int max){
        if(root == NULL) return NULL;
        if(root->val == min || root->val == max)
            return root;
        TreeNode* left_lca = findLCA(root->left, min, max);
        TreeNode* right_lca = findLCA(root->right, min, max);
        if(left_lca != NULL && right_lca != NULL)
            return root;
        return left_lca != NULL ? left_lca : right_lca;
    }

    // return level of node, root level is 0.
    int findLevel(TreeNode* root, int val){
        if(root == NULL) return -1;
        if(root->val == val) return 0;
        int level = findLevel(root->left, val);
        if(level == -1){
            level = findLevel(root->right, val);
        }
        if(level != -1)
            return level + 1;
        return -1;
    }

    int getDis(TreeNode* root) {
        // write code here
        current_min = INT_MAX;
        current_max = INT_MIN;
        findMinMax(root);
        TreeNode* lca = findLCA(root, current_min, current_max);
        int lev_lca = findLevel(root, lca->val);
        int lev_min = findLevel(root, current_min);
        int lev_max = findLevel(root, current_max);
        return lev_min + lev_max - 2*lev_lca;


    }
};

3 寻找第K大

有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。
测试样例:
[1,3,5,2,2],5,3
返回:2

class Finder {
public:
    int findKth(vector<int> a, int n, int K) {
        // write code here
    }
};

直接思路就是排序,然后找对应位置,比较简单

3.1 复习 快速排序

// 快速排序, 交换次数较少的实现
#include <iostream>
#include <algorithm>
using namespace std;

void printArrayInt(int arr_int[], int size){
    for(int i = 0; i < size; ++i){
        cout << arr_int[i] << " ";
    }
    cout << endl;
}

//划分操作
int partition(int arr[], int start, int end) {
    int i = start - 1;//指向开头
    int j = end;//指向结尾
    int pivot = arr[end];
    while(true){
        while(i < end && arr[++i] < pivot);//从前到后  第一个比 基准(x)大的数。 i指向该数
        while(j > start && arr[--j] > pivot);//从后向前找到第一个比 基准(x)小的数。j指向该数
        if(i >= j)
            break;
        swap(arr[i], arr[j]);
    }
    swap(arr[i], arr[end]);
    return i;
}

void quickSort(int arr_int[], int start, int end){
    if(start < end){
        int partition_index = partition(arr_int, start, end);
        quickSort(arr_int, start, partition_index - 1);
        quickSort(arr_int, partition_index + 1, end);
    }
}

int main()
{


    int arr_int[] = {10, 7, 8, 9, 1, 5};
    int arr_size = sizeof(arr_int) / sizeof(arr_int[0]);
    quickSort(arr_int, 0, arr_size-1);

    return 0;
}

3.2 第1次提交 快排 AC:

#include <iostream>
#include <algorithm>
using namespace std;

class Finder {
public:

    //划分操作
    int partition(int arr[], int start, int end) {
        int i = start - 1;//指向开头
        int j = end;//指向结尾
        int pivot = arr[end];
        while(true){
            while(i < end && arr[++i] < pivot);//从前到后  第一个比 基准(x)大的数。 i指向该数
            while(j > start && arr[--j] > pivot);//从后向前找到第一个比 基准(x)小的数。j指向该数
            if(i >= j)
                break;
            swap(arr[i], arr[j]);
        }
        swap(arr[i], arr[end]);
        return i;
    }

    void quickSort(int arr_int[], int start, int end){
        if(start < end){
            int partition_index = partition(arr_int, start, end);
            quickSort(arr_int, start, partition_index - 1);
            quickSort(arr_int, partition_index + 1, end);
        }
    }

    int findKth(vector<int> a, int n, int K) {
        int* arr_int = new int[n];
        for(int i = 0; i < n; ++i){
            arr_int[i] = a[i];
        }
        quickSort(arr_int, 0, n - 1);
        return arr_int[n - K];
    }
};

快排的时间复杂度为O(nlogn), 比较慢,

更快的方法:
创建一个大小为k的数据容器,存储k个最大的数字,过程:
a) 若容器中数字不足k个,则放入容器
b) 若容器已经满了,则找出k个数字中最小的值,若当前值比容器中的最小值大,则替换容器中的最小值,否则抛弃当前值。

容器满了之后,要做三件事:
(1)在k个整数中找到最小值
(2)有可能删除最小值
(3)有可能插入新的值,并保证k个整数是已排序。

所以用二叉树实现容器,则三种操作可在O(logk)时间内完成,对于n个数字,总的时间是O(nlogk)

由于每次都要取出最小值,且容器保持排序状态,所以想到最小堆, 直接使用STL中基于红黑树的multiset

3.3 第2次提交 最小堆 AC

// 最终提交:
#include <vector>
#include <set>
class Finder {
public:
    typedef multiset<int> MinHeapInt;

void find_kth_max_int(const vector<int>& vec_int, MinHeapInt& largestNumbers, int k){

    largestNumbers.clear();

    if(k <= 0 || vec_int.size() < k)
        return;

    vector<int>::const_iterator iter = vec_int.begin();
    for(; iter != vec_int.end(); ++iter){
        // 最小堆未满时
        if(largestNumbers.size() < k){
            largestNumbers.insert(*iter);
        }
        else{
            // 最大堆已满
            MinHeapInt::iterator iterFirst = largestNumbers.begin();
            // 当前元素大于堆中最小值时,替换
            if(*iter > *(largestNumbers.begin())){
                largestNumbers.erase(iterFirst);
                largestNumbers.insert(*iter);
            }
        }
    }
}

    int findKth(vector<int> a, int n, int K) {
        // write code here
        MinHeapInt largestNumbers;
        find_kth_max_int(a, largestNumbers, K);
        return *(largestNumbers.begin());
    }
};

附: 测试程序

#include <vector>
#include <set>
#include <iostream>
using namespace std;

typedef multiset<int> MinHeapInt;

void find_kth_max_int(const vector<int>& vec_int, MinHeapInt& largestNumbers, int k){

    largestNumbers.clear();

    if(k <= 0 || vec_int.size() < k)
        return;

    vector<int>::const_iterator iter = vec_int.begin();
    for(; iter != vec_int.end(); ++iter){
        // 最小堆未满时
        if(largestNumbers.size() < k){
            largestNumbers.insert(*iter);
        }
        else{
            // 最大堆已满
            MinHeapInt::iterator iterFirst = largestNumbers.begin();
            // 当前元素大于堆中最小值时,替换
            if(*iter > *(largestNumbers.begin())){
                largestNumbers.erase(iterFirst);
                largestNumbers.insert(*iter);
            }
        }
    }
}


int main(){
    vector<int> data;
    data.push_back(10);
    data.push_back(1);
    data.push_back(7);
    data.push_back(3);
    data.push_back(4);
    data.push_back(8);
    data.push_back(5);
    data.push_back(11);
    data.push_back(12);
    MinHeapInt largestNumbers;
    int k = 4;
    find_kth_max_int(data, largestNumbers, k);
    // for test
    multiset<int>::const_iterator iter = largestNumbers.begin();
    for(; iter!=largestNumbers.end(); ++iter){
        cout << *iter << " ";
    }
    cout << endl;
    cout << "第" << k << "大的数为" << *(largestNumbers.begin()) << endl;
    return 0;
}

3.4 第3次提交 半排序 AC

中间提交了两次,其中一次发现少了return语句,因为半排序的递归过程也是要写return的,一旦到达kth_max位置,就直接return了。第二次是发现查错了kth_max的下标。

// 半排序思想
/*
快排的分割过程返回pivot_index位置时(从0计数),
左侧有pivot_index 个比pivot_value小的数,
右侧有n - pivot_index-1个比pivot_value大的数,
所以pivot_value本身是第n-pivot_index大的数,
当pivot_index == n-k时,pivot_value为第k大的数
所以kth_max的index为n-k,设为kth_index

递归过程类似于二分查找,当pivot_index==kth_index时,程序结束,返回结果
否则,继续在kth max所在的左侧或右侧继续分治排序(类似于二分查找)
*/

#include <algorithm>
using namespace std;
class Finder {
public:
 int partition(int arr_int[], int start, int end){
    int pivot_value = arr_int[end];
    int i = start - 1;
    int j = end;
    while(true){
        while(i < end && arr_int[++i] < pivot_value);
        while(j > start && arr_int[--j] > pivot_value);
        if(i >= j)
            break;
        swap(arr_int[i], arr_int[j]);
    }
    swap(arr_int[i], arr_int[end]);
    return i;
}

int quick_sort_find_kth_max(int arr_int[], int start, int end, int k){

    int pivot_index = partition(arr_int, start, end);
    // kth max 在pivot_index左侧,右侧都比kth max大,不用管,排左侧
    if(pivot_index > k){
        return quick_sort_find_kth_max(arr_int, start, pivot_index - 1, k);
    }
    // kth max 在pivot_index右侧,左侧逗比kth max小,不用管,排右侧
    else if(pivot_index < k){
        return quick_sort_find_kth_max(arr_int, pivot_index + 1, end, k);
    }
    else{
        return arr_int[pivot_index];
    }
}

int findKth(vector<int> a, int n, int K) {
    // write code here
    int* arr_int = new int[n];
    for(int i = 0; i < n; ++i){
        arr_int[i] = a[i];
    }
    return quick_sort_find_kth_max(arr_int, 0, n-1, n-K);// 1,2,3,第3大的是1,所以kth max的index是n-k

}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值