牛客编程题--必刷101之模拟

当前这个专题是最后一个版块(模拟),这个专题结束后还会出现剑指offer专题,希望能够对小伙伴们笔试有所帮助,早日拿到offer!

前言

所谓的模拟题,运用的“模拟算法”,其实并没有什么完全准确的定义。模拟算法,用一句老话说,就是“照着葫芦画瓢”;官方化的诠释则是:根据题目表述进行筛选提取关键要素,按需求书写代码解决实际问题。

模拟这个算法其实并不难,主要是逻辑上的麻烦,但正常刷题时我们都不把模拟的逻辑思维理清就直接做,如果这题没有太水的话,是非常容易错的。

特点

●码量大
●操作多
●思路繁复杂
●较为复杂的模拟题,出错后难以定位错误

技巧

●在动手写代码之前,在草纸上尽可能地写好要实现的流程.
●在代码中,尽量把每个部分模块化,写成函数、结构体或类.
●对于一些可能重复用到的概念,可以统-转化,方便处理:如,某题给你"YY-MM-DD时:分"把它抽取到一
个函数,处理成秒,会减少概念混淆
●调试时分块调试。模块化的好处就是可以方便的单独调某一部分。
●写代码的时候一定要思路清晰, 不要想到什么写什么,要按照落在纸上的步骤写。

具体详细内容可见博客:https://blog.csdn.net/qq_61386381/article/details/123264225

旋转数组

题目描述:一个数组A中存有 n 个整数,在不允许使用另外数组的前提下,将每个整数循环向右移 M( M >=0)个位置,即将A中的数据由(A0 A1 ……AN-1 )变换为(AN-M …… AN-1 A0 A1 ……AN-M-1 )(最后 M 个数循环移至最前面的 M 个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?

示例:
输入:6,2,[1,2,3,4,5,6]
返回值:[5,6,1,2,3,4]

思路:循环右移相当于从第m个位置开始,左右两部分视作整体翻转。即abcdefg右移3位efgabcd可以看成AB翻转成BA(这里小写字母看成数组元素,大写字母看成整体)。既然是翻转我们就可以用到reverse函数。

具体步骤:
step 1:因为mmm可能大于nnn,因此需要对nnn取余,因为每次长度为nnn的旋转数组相当于没有变化。
step 2:第一次将整个数组翻转,得到数组的逆序,它已经满足了右移的整体出现在了左边。
step 3:第二次就将左边的mmm个元素单独翻转,因为它虽然移到了左边,但是逆序了。
step 4:第三次就将右边的n−mn-mn−m个元素单独翻转,因此这部分也逆序了。
在这里插入图片描述

import java.util.*;


public class Solution {
    /**
     * 旋转数组
     * @param n int整型 数组长度
     * @param m int整型 右移距离
     * @param a int整型一维数组 给定数组
     * @return int整型一维数组
     */
    public int[] solve (int n, int m, int[] a) {
        // write code here
        m = m % n ;
        // 逆转全部数组元素
        reverse(a, 0, n-1);
        // 逆转开头几个元素
        reverse(a, 0, m-1);
        // 逆转结尾的几个元素
        reverse(a, m, n-1);
        
        return a;
        
    }
    
    public void reverse(int [] nums, int start, int end){
        while(start < end){
            swap(nums, start++, end--);
        }
    }
    
    // swap 函数
    public void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

螺旋矩阵

题目描述:给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。

示例:
输入: [[1,2,3],[4,5,6],[7,8,9]]
返回值: [1,2,3,6,9,8,7,4,5]

思路:简单模拟,我们想象有一个矩阵,从第一个元素开始,往右到底后再往下到底后再往左到底后再往上,结束这一圈,进入下一圈螺旋。

具体步骤:
step 1:首先排除特殊情况,即矩阵为空的情况。
step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
step 7:重复上述3-6步骤直到循环结束。在这里插入图片描述

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> spiralOrder(int[][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
        // 排除特殊情况
        if(matrix.length == 0){
            return res;
        }
        // 左边
        int left = 0;
        // 右边
        int right = matrix[0].length -1;
        // 上边
        int up = 0;
        // 下边
        int down = matrix.length -1;
        // 直到边界重合
        while(left <= right && up <= down){
            //上边界从左到右
            for(int i = left; i<= right; i++){
                res.add(matrix[up][i]);
            }
            //继续向下
            up ++;
            if(up > down)
                break;
            // 右边界从上到下
            for(int i = up; i<=down; i++)
                res.add(matrix[i][right]);
            // 右边界向左
            right--;
            if(left > right)
                break;
            // 下边界从右往左
            for(int i = right ; i>=left; i--){
                res.add(matrix[down][i]);
            }
            //下边界向上
            down--;
            if(up > down){
                break;
            }
            // 左边界从下到上
            for(int i=down; i>=up;i--)
                res.add(matrix[i][left]);
            // 左边界向右
            left++;
            if(left > right)
                break;
        }
        return res;
    }
}

顺时针旋转矩阵

题目描述:有一个nxn整数矩阵,请编写一个算法,将矩阵顺时针旋转90度。
给定一个nxn的矩阵,和矩阵的阶数n,请返回旋转后的nxn矩阵。

示例:
输入:[[1,2,3],[4,5,6],[7,8,9]],3
返回值:[[7,4,1],[8,5,2],[9,6,3]]

这个题目没有技巧,需要将原矩阵和转换后的矩阵画出来
在这里插入图片描述
在这里插入图片描述
这就是互为转置的两个矩阵。因为转置的可逆性,只要过程逆转,就可以得到顺时针旋转90度后的矩阵了。

具体步骤:
step 1:遍历矩阵的下三角矩阵,将其与上三角矩阵对应的位置互换,其实就是数组下标交换后的互换。
step 2:遍历矩阵每一行,将每一行看成一个数组使用reverse函数翻转。

import java.util.*;

public class Solution {
    public int[][] rotateMatrix(int[][] mat, int n) {
        // write code here
        int length = mat.length;
        //矩阵转置
        for(int i=0; i< length; i++){
            for(int j=0; j<i ; j++){
                // 交换
                int temp =mat[i][j];
                mat[i][j] = mat[j][i];
                mat[j][i] = temp;
            }
        }
        // 进行每行翻转
        for(int i =0 ; i < length; i++){
            for(int j=0; j< length/2; j++){
                int temp = mat[i][j];
                mat[i][j] = mat[i][length -j-1];
                mat[i][length-j-1] = temp;
            }
        }
        return mat;
    }
}

设计LRU缓存结构

题目描述:
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:

  1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
  3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value

输入:
[“set”,“set”,“get”,“set”,“get”,“set”,“get”,“get”,“get”],[[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]],2
返回值:
[“null”,“null”,“1”,“null”,“-1”,“null”,“-1”,“3”,“4”]
说明:
我们将缓存看成一个队列,最后一个参数为2代表capacity,所以
Solution s = new Solution(2);
s.set(1,1); //将(1,1)插入缓存,缓存是{“1”=1},set操作返回"null"
s.set(2,2); //将(2,2)插入缓存,缓存是{“2”=2,“1”=1},set操作返回"null"
output=s.get(1);// 因为get(1)操作,缓存更新,缓存是{“1”=1,“2”=2},get操作返回"1"
s.set(3,3); //将(3,3)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{“3”=3,“1”=1},set操作返回"null"
output=s.get(2);// 因为get(2)操作,不存在对应的key,故get操作返回"-1"
s.set(4,4); //将(4,4)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{“4”=4,“3”=3},set操作返回"null"
output=s.get(1);// 因为get(1)操作,不存在对应的key,故get操作返回"-1"
output=s.get(3);//因为get(3)操作,缓存更新,缓存是{“3”=3,“4”=4},get操作返回"3"
output=s.get(4);//因为get(4)操作,缓存更新,缓存是{“4”=4,“3”=3},get操作返回"4"

思路:哈希表+双向链表

知识点1:哈希表

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

知识点2:双向链表

双向链表是一种特殊的链表,它除了链表具有的每个节点指向后一个节点的指针外,还拥有一个每个节点指向前一个节点的指针,因此它可以任意向前或者向后访问,每次更改节点连接状态的时候,需要变动两个指针。

插入与访问值都是O(1),没有任何一种数据结构可以直接做到。
于是我们可以想到数据结构的组合:访问O(1)很容易想到了哈希表;插入O(1)的数据结构有很多,但是如果访问到了这个地方再选择插入,且超出长度要在O(1)之内删除,我们可以想到用链表,可以用哈希表的key值对应链表的节点,完成直接访问。但是我们还需要把每次访问的key值节点加入链表头,同时删掉链表尾,所以选择双向链表,便于删除与移动。
在这里插入图片描述
具体步骤:
step 1:构建一个双向链表的类,记录key值与val值,同时一前一后两个指针。用哈希表存储key值和链表节点,这样我们可以根据key值在哈希表中直接锁定链表节点,从而实现在链表中直接访问,能够做到O(1)时间访问链表任意节点。

//设置双向链表结构
class Node{
    int key;
    int val;
    Node pre;
    Node next;
    //初始化
    public Node(int key, int val) {
        this.key = key;
        this.val = val;
        this.pre = null;
        this.next = null;
    }
}

step 2:设置全局变量,记录双向链表的头、尾及LRU剩余的大小,并全部初始化,首尾相互连接好。

//构建初始化连接
//链表剩余大小
this.size = k;
this.head.next = this.tail;
this.tail.pre = this.head;

step 3:遍历函数的操作数组,检查第一个元素判断是属于set操作还是get操作。
step 4:如果是set操作,即将key值与val值加入链表,我们先检查链表中是否有这个key值,可以通过哈希表检查出,如果有直接通过哈希表访问链表的相应节点,修改val值,并将访问过的节点移到表头;如果没有则需要新建节点加到表头,同时哈希表中增加相应key值(当然,还需要检查链表长度还有无剩余,若是没有剩余则需要删去链表尾)。

//没有见过这个key,新值加入
if(!mp.containsKey(key)){
    Node node = new Node(key, val);
    mp.put(key, node);
    //超出大小,移除最后一个
    if(size <= 0)
        removeLast();
    //大小还有剩余
    else
        //大小减1
        size--;
    //加到链表头
    insertFirst(node);
}
//哈希表中已经有了,即链表里也已经有了
else{ 
    mp.get(key).val = val;
    //访问过后,移到表头
    moveToHead(mp.get(key));
}

step 5:不管是新节点,还是访问过的节点都需要加到表头,若是访问过的,需要断开原来的连接,再插入表头head的后面。

//移到表头函数
void moveToHead(Node node){
    //已经到了表头
    if(node.pre == head) 
        return;
    //将节点断开,取出来
    node.pre.next = node.next;
    node.next.pre = node.pre;
    //插入第一个前面
    insertFirst(node);
}

step 6:删除链表尾需要断掉尾节点前的连接,同时哈希表中去掉这个key值。

void removeLast(){
    //哈希表去掉key
    mp.remove(tail.pre.key);
    //断连该节点
    tail.pre.pre.next = tail;
    tail.pre = tail.pre.pre;
}

step 7:如果是get操作,检验哈希表中有无这个key值,如果没有说明链表中也没有,返回-1,否则可以根据哈希表直接锁定链表中的位置进行访问,然后重复step 5,访问过的节点加入表头。

if(mp.containsKey(key)){
    Node node = mp.get(key);
    res = node.val;
    moveToHead(node);
}
import java.util.*;
public class Solution {
    //设置双向链表结构
    static class Node{
        int key;
        int val;
        Node pre;
        Node next;
        //初始化
        public Node(int key, int val) {
            this.key = key;
            this.val = val;
            this.pre = null;
            this.next = null;
        }
    }
     
    //哈希表
    private Map<Integer, Node> mp = new HashMap<>();
    //设置一个头
    private Node head = new Node(-1, -1);
    //设置一个尾
    private Node tail = new Node(-1, -1);
    private int size = 0;
     
    public int[] LRU (int[][] operators, int k) {
        //构建初始化连接
        //链表剩余大小
        this.size = k;
        this.head.next = this.tail;
        this.tail.pre = this.head;
        //获取操作数
        int len = (int)Arrays.stream(operators).filter(x -> x[0] == 2).count();
        int[] res = new int[len];
        //遍历所有操作
        for(int i = 0, j = 0; i < operators.length; i++){
            if(operators[i][0] == 1)
                //set操作
                set(operators[i][1], operators[i][2]);
            else
                //get操作
                res[j++] = get(operators[i][1]);
        }
        return res;
    }
     
     //插入函数
    private void set(int key, int val){
        //没有见过这个key,新值加入
        if(!mp.containsKey(key)){
            Node node = new Node(key, val);
            mp.put(key, node);
            //超出大小,移除最后一个
            if(size <= 0)
                removeLast();
            //大小还有剩余
            else
                //大小减1
                size--;
            //加到链表头
            insertFirst(node);
        }
        //哈希表中已经有了,即链表里也已经有了
        else{ 
            mp.get(key).val = val;
            //访问过后,移到表头
            moveToHead(mp.get(key));
        }
    }
     
    //获取数据函数
    private int get(int key){
        int res = -1;
        if(mp.containsKey(key)){
            Node node = mp.get(key);
            res = node.val;
            moveToHead(node);
        }
        return res;
    }
    //移到表头函数
    private void moveToHead(Node node){
        //已经到了表头
        if(node.pre == head) 
            return;
        //将节点断开,取出来
        node.pre.next = node.next;
        node.next.pre = node.pre;
        //插入第一个前面
        insertFirst(node);
    }
     
    //将节点插入表头函数
    private void insertFirst(Node node){
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }
     
    //删去表尾函数,最近最少使用
    private void removeLast(){
        //哈希表去掉key
        mp.remove(tail.pre.key);
        //断连该节点
        tail.pre.pre.next = tail;
        tail.pre = tail.pre.pre;
    }
}
=====================================================================
import java.util.*;
 
class Node {
    public int key;
    public int value;
    public Node front;
    public Node next;
    public Node(int key, int value) {
        this.key = key;
        this.value = value;
        this.front = null;
        this.next = null;
    }
}
 
public class Solution {
    private int size;
    private int capacity;
    private Node start;
    private Node end;
    private Map<Integer, Node> map;
 
    public Solution(int capacity) {
        // write code here
        this.capacity = capacity;
        this.size = 0;
        this.map = new HashMap<>(capacity + 1);
        this.start = new Node(0, 0);
        this.end = new Node(0, 0);
        this.start.front = this.end;
        this.start.next = this.end;
        this.end.front = this.start;
        this.end.next = this.start;
    }
 
    public int get(int key) {
        // write code here
        if (!map.containsKey(key)) {
            return -1;
        }
        Node node = this.removeFromLink(map.get(key));
        this.insertIntoStart(node);
        return node.value;
    }
 
    public void set(int key, int value) {
        // write code here
        if (this.map.containsKey(key)) {
            Node node = this.removeFromLink(map.get(key));
            node.value = value;
            this.insertIntoStart(node);
            return;
        }
        Node newNode = new Node(key, value);
        this.insertIntoStart(newNode);
        map.put(newNode.key, newNode);
        this.size++;
        if (this.size > this.capacity) {
            Node deleteNode = this.removeFromLink(this.end.front);
            map.remove(deleteNode.key);
            this.size--;
        }
    }
 
    private Node removeFromLink(Node node) {
        Node before = node.front, after = node.next;
        before.next = after;
        after.front = before;
        node.front = null;
        node.next = null;
        return node;
    }
 
    private void insertIntoStart(Node node) {
        Node temp  = this.start.next;
        this.start.next = node;
        node.front = this.start;
        temp.front = node;
        node.next = temp;
    }
}
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

研行笔录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值