校招算法笔面试 | 多数组中位数

题目## 题目## 题目

题目链接

解题思路

为了找到两个升序数组合并后的下中位数,我们可以使用双指针的方法。具体步骤如下:

  1. 合并两个数组

    • 使用两个指针分别指向两个数组的起始位置,逐步比较并合并两个数组。
  2. 计算中位数

    • 由于我们只需要下中位数,可以在合并过程中只记录前半部分的元素。
    • 当合并的元素个数达到 (len1 + len2) / 2 时,返回当前的元素。
  3. 处理偶数情况

    • 如果合并后的数组长度为偶数,返回中间两个元素中的较小值。

代码

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

class Solution {
public:
    int getUpMedian(vector<int>& arr1, vector<int>& arr2) {
        int len1 = arr1.size(), len2 = arr2.size();
        int totalLength = len1 + len2;
        int midIndex = totalLength / 2;
        int i = 0, j = 0, current = 0, last = 0;

        while (i < len1 || j < len2) {
            last = current; // 记录上一个元素
            if (i < len1 && (j >= len2 || arr1[i] <= arr2[j])) {
                current = arr1[i++];
            } else {
                current = arr2[j++];
            }

            // 当合并的元素个数达到中位数位置时返回
            if (i + j - 1 == midIndex) {
                if (totalLength % 2 == 0) {
                    return min(current, last); // 偶数情况,返回较小的
                } else {
                    return current; // 奇数情况,返回当前元素
                }
            }
        }
        return -1; // 这行代码理论上不会被执行
    }
};
import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param arr1 int整型一维数组 
     * @param arr2 int整型一维数组 
     * @return int整型
     */
    public int getUpMedian(int[] arr1, int[] arr2) {
        int len1 = arr1.length, len2 = arr2.length;
        int totalLength = len1 + len2;
        int midIndex = totalLength / 2;
        int i = 0, j = 0, current = 0, last = 0;

        while (i < len1 || j < len2) {
            last = current; // 记录上一个元素
            if (i < len1 && (j >= len2 || arr1[i] <= arr2[j])) {
                current = arr1[i++];
            } else {
                current = arr2[j++];
            }

            // 当合并的元素个数达到中位数位置时返回
            if (i + j - 1 == midIndex) {
                if (totalLength % 2 == 0) {
                    return Math.min(current, last); // 偶数情况,返回较小的
                } else {
                    return current; // 奇数情况,返回当前元素
                }
            }
        }
        return -1; // 这行代码理论上不会被执行
    }
}
class Solution:
    def getUpMedian(self, arr1, arr2):
        len1, len2 = len(arr1), len(arr2)
        total_length = len1 + len2
        mid_index = total_length // 2
        i, j = 0, 0
        current, last = 0, 0

        while i < len1 or j < len2:
            last = current  # 记录上一个元素
            if i < len1 and (j >= len2 or arr1[i] <= arr2[j]):
                current = arr1[i]
                i += 1
            else:
                current = arr2[j]
                j += 1

            # 当合并的元素个数达到中位数位置时返回
            if i + j - 1 == mid_index:
                if total_length % 2 == 0:
                    return min(current, last)  # 偶数情况,返回较小的
                else:
                    return current  # 奇数情况,返回当前元素

        return -1  # 这行代码理论上不会被执行


算法及复杂度

  • 算法:双指针
  • 时间复杂度: O ( n + m ) \mathcal{O(n + m)} O(n+m),其中 n n n m m m 分别是两个数组的长度
  • 空间复杂度: O ( 1 ) \mathcal{O(1)} O(1),只使用了常数级的额外空间

题目链接

题目的主要信息:
  • 一个复杂链表除了有指向后一个节点的指针,还有一个指针随机节点的指针
  • 将该复杂链表拷贝,返回拷贝链表的头节点
  • 拷贝链表必须创建新的节点
举一反三:

学习完本题的思路你可以解决如下题目:

JZ18. 删除链表的节点

JZ6. 从尾到头打印链表

JZ52. 两个链表的第一个公共节点

JZ23. 链表环的入口

JZ25. 合并两个有序链表

方法一:组合链表(推荐使用)

知识点:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

正常链表的复制,从头到尾遍历链表,对于每个节点创建新的节点,赋值,并将其连接好就可以了。这道题的不同之处在于我们还要将随机指针连接好,我们创建节点的时候,有可能这个节点创建了,但是它的随机指针指向的节点没有创建,因此创建的时候只能连接指向后面的指针,无法连接随机指针。

等链表连接好了,再连接随机指针的话,我们又难以找到这个指针指向的位置,因为链表不支持随机访问。但是吧,我们待拷贝的链表可以通过随机指针访问节点,那么我们不如将拷贝后的每个节点插入到原始链表相应节点之后,这样连接random指针的时候,原始链表random指针后一个元素就是原始链表要找的随机节点,而该节点后一个就是它拷贝出来的新节点,这不就可以连上了嘛。

//跟随前一个连接random
if(cur.random == null)
    clone.random = null;
else
    //后一个节点才是拷贝的
    clone.random = cur.random.next;

这样等随机指针链表完成之后,再遍历链表,将其拆分按照奇偶序列拆分成两个链表:只需要每次越过相邻节点连接就可以了。

//cur.next必定不为空
cur.next = cur.next.next;
cur = cur.next;
//检查末尾节点
if(clone.next != null)
    clone.next = clone.next.next;
clone = clone.next;

具体做法:

  • step 1:遍历链表,对每个节点新建一个拷贝节点,并插入到该节点之后。
  • step 2:使用双指针再次遍历链表,两个指针每次都移动两步,一个指针遍历原始节点,一个指针遍历拷贝节点,拷贝节点的随机指针跟随原始节点,指向原始节点随机指针的下一位。
  • step 3:再次使用双指针遍历链表,每次越过后一位相连,即拆分成两个链表。

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        //空节点直接返回
        if(pHead == null)
            return pHead;
        //添加一个头部节点
        RandomListNode cur = pHead;
        //遍历原始链表,开始复制
        while(cur != null){
            //拷贝节点
            RandomListNode clone = new RandomListNode(cur.label);
            //将新节点插入到被拷贝的节点后
            clone.next = cur.next;
            cur.next = clone;
            cur = clone.next;
        }
        cur = pHead;
        RandomListNode clone = pHead.next;
        RandomListNode res = pHead.next;
        //连接新链表的random节点
        while(cur != null){
            //跟随前一个连接random
            if(cur.random == null)
                clone.random = null;
            else
                //后一个节点才是拷贝的
                clone.random = cur.random.next;
            //cur.next必定不为空
            cur = cur.next.next;
            //检查末尾节点
            if(clone.next != null)
                clone = clone.next.next;
        }
        cur = pHead;
        clone = pHead.next;
        //拆分两个链表
        while(cur != null){
            //cur.next必定不为空
            cur.next = cur.next.next;
            cur = cur.next;
            //检查末尾节点
            if(clone.next != null)
                clone.next = clone.next.next;
            clone = clone.next;
        }
        return res;
    }
}

C++实现代码:

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        //空节点直接返回
        if(pHead == NULL)
            return pHead;
        //添加一个头部节点
        RandomListNode *cur = pHead;
        //遍历原始链表,开始复制
        while(cur != NULL){
            //拷贝节点
            RandomListNode *clone = new RandomListNode(cur->label);
            //将新节点插入到被拷贝的节点后
            clone->next = cur->next;
            cur->next = clone;
            cur = clone->next;
        }
        cur = pHead;
        RandomListNode *clone = pHead->next;
        RandomListNode *res = pHead->next;
        //连接新链表的random节点
        while(cur != NULL){
            //跟随前一个连接random
            if(cur->random == NULL)
                clone->random = NULL;
            else
                //后一个节点才是拷贝的
                clone->random = cur->random->next;
            //cur->next必定不为空
            cur = cur->next->next;
            //检查末尾节点
            if(clone->next != NULL)
                clone = clone->next->next;
        }
        cur = pHead;
        clone = pHead->next;
        //拆分两个链表
        while(cur != NULL){
            //cur->next必定不为空
            cur->next = cur->next->next;
            cur = cur->next;
            //检查末尾节点
            if(clone->next != NULL)
                clone->next = clone->next->next;
            clone = clone->next;
        }
        return res;
    }
};

Python实现代码:

class Solution:
    def Clone(self, pHead):
        #空节点直接返回
        if pHead is None:
            return pHead
        #添加一个头部节点
        cur = pHead
        #遍历原始链表,开始复制
        while cur is not None:
            #拷贝节点
            clone = RandomListNode(cur.label)
            #将新节点插入到被拷贝的节点后
            clone.next = cur.next
            cur.next = clone
            cur = clone.next
        cur = pHead
        clone = pHead.next
        res = pHead.next
        #连接新链表的random节点
        while cur is not None:
            #跟随前一个连接random
            if cur.random is None:
                clone.random = None
            else:
                #后一个节点才是拷贝的
                clone.random = cur.random.next
            #cur.next必定不为空
            cur = cur.next.next
            #检查末尾节点
            if clone.next is not None:
                clone = clone.next.next
        cur = pHead
        clone = pHead.next
        #拆分两个链表
        while cur is not None:
            #cur.next必定不为空
            cur.next = cur.next.next
            cur = cur.next
            #检查末尾节点
            if clone.next is not None:
                clone.next = clone.next.next
            clone = clone.next
        return res

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为链表长度,遍历三次链表,第一次遍历 n n n个节点,第二次、第三次遍历 2 n 2n 2n个节点
  • 空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间
方法二:哈希表(扩展思路)

知识点:哈希表

哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在 O ( 1 ) O(1) O(1)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。

思路:

同样的,我们正常拷贝链表节点,只要能够保证我们可以随机访问就行了。因此我们可以考虑哈希表,利用哈希表对链表原始节点和拷贝节点之间建立映射关系,因此原始链表支持随机指针访问,这样我们建立映射以后,可以借助哈希表与原始链表的随机指针,在拷贝链表上随机访问。

具体做法:

  • step 1:建立哈希表,key为原始链表的节点,value为拷贝链表的节点。
  • step 2:遍历原始链表,依次拷贝每个节点,并连接指向后一个的指针,同时将原始链表节点与拷贝链表节点之间的映射关系加入哈希表。
  • step 3:遍历哈希表,对于每个映射,拷贝节点的ramdom指针就指向哈希表中原始链表的random指针。

Java实现代码:

import java.util.*;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        //空节点直接返回
        if(pHead == null)
            return pHead;
        //添加一个头部节点
        RandomListNode res = new RandomListNode(0);
        //哈希表,key为原始链表的节点,value为拷贝链表的节点
        HashMap<RandomListNode, RandomListNode> mp = new HashMap<>();
        RandomListNode cur = pHead;
        RandomListNode pre = res;
        //遍历原始链表,开始复制
        while(cur != null){
            //拷贝节点
            RandomListNode clone = new RandomListNode(cur.label);
            //用哈希表记录该节点下的拷贝节点
            mp.put(cur, clone);
            //连接
            pre.next = clone;
            pre = pre.next;
            //遍历
            cur = cur.next;
        }
        //遍历哈希表
        for(HashMap.Entry<RandomListNode, RandomListNode> entry : mp.entrySet()){
            //原始链表中random为空
            if(entry.getKey().random == null)
                entry.getValue().random = null;
            else
                //将新链表的random指向哈希表中原始链表的random
                entry.getValue().random = mp.get(entry.getKey().random); 
        }
        //返回去掉头
        return res.next;
    }
}

C++实现代码:

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        //空节点直接返回
        if(pHead == NULL)
            return pHead;
        //添加一个头部节点
        RandomListNode *res = new RandomListNode(0);
        //哈希表,key为原始链表的节点,value为拷贝链表的节点
        unordered_map<RandomListNode*, RandomListNode*> mp;
        RandomListNode *cur = pHead;
        RandomListNode *pre = res;
        //遍历原始链表,开始复制
        while(cur != NULL){
            //拷贝节点
            RandomListNode *clone = new RandomListNode(cur->label);
            //用哈希表记录该节点下的拷贝节点
            mp[cur] = clone;
            //连接
            pre->next = clone;
            pre = pre->next;
            //遍历
            cur = cur->next;
        }
        //遍历哈希表
        for(auto node: mp){
            //原始链表中random为空
            if(node.first->random == NULL)
                node.second->random = NULL;
            else
                //将新链表的random指向哈希表中原始链表的random
                node.second->random = mp[node.first->random]; 
        }
        //返回去掉头
        return res->next;
    }
};

Python实现代码:

class Solution:
    def Clone(self, pHead):
        #空节点直接返回
        if pHead is None:
            return pHead
        #添加一个头部节点
        res = RandomListNode(0)
        #哈希表,key为原始链表的节点,value为拷贝链表的节点
        mp = dict()
        cur = pHead
        pre = res
        #遍历原始链表,开始复制
        while cur is not None:
            #拷贝节点
            clone = RandomListNode(cur.label)
            #用哈希表记录该节点下的拷贝节点
            mp[cur] = clone
            #连接
            pre.next = clone
            pre = pre.next
            #遍历
            cur = cur.next
        #遍历哈希表
        for (key, value) in mp.items():
            #原始链表中random为空
            if key.random is None:
                value.random = None
            else:
                #将新链表的random指向哈希表中原始链表的random
                value.random = mp[key.random] 
        #返回去掉头
        return res.next

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为链表的长度,遍历一次链表再遍历一次哈希表
  • 空间复杂度: O ( n ) O(n) O(n),哈希表的大小为链表长度

题目链接

#描述
这是一篇针对初学者的题解。
知识点:栈
难度:一星

#题解
题目抽象:要求实现一个O(1)时间复杂度的返回最小值的栈。正常情况下,栈的push,pop操作都为O(1),
但是返回最小值,需要遍历整个栈,时间复杂度为O(n),所以这里需要空间换时间的思想。

##方法:使用辅助栈
首先需要一个正常栈normal,用于栈的正常操作,然后需要一个辅助栈minval,专门用于获取最小值,具体操作如下。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • push操作就如图片上操作。
  • pop操作直接对应弹出就好了。
  • top操作就返回normal的栈顶
  • min操作就返回minval的栈顶

因此,代码如下:

class Solution {
public:
    stack<int> normal, minval;
    void push(int value) {
        normal.push(value);
        if (minval.empty()) {
            minval.push(value);
        }
        else {
            if (value &lt;= minval.top()) {
                minval.push(value);
            }
            else {
                minval.push(minval.top());
            }
        }
    }
    void pop() {
        normal.pop();
        minval.pop();
    }
    int top() {
        return normal.top();
    }
    int min() {
        return minval.top();
    }
};

时间复杂度:O(1)
空间复杂度:O(n), 开辟了一个辅助栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值