17数据结构与算法刷题之【模拟题】篇

简介: 17数据结构与算法刷题之【模拟题】篇

剑指offer


剑指 Offer 29. 顺时针打印矩阵【简单】


题目链接:剑指 Offer 29. 顺时针打印矩阵


题目内容:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。


思路:


1、找规律,四个方位的下标不断进行平移


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
    //  1 2 3
    //  4 5 6
    //  7 8 9
    // 123(00、01、02)  69(12、22)  87(21、20)  4(10)  5(11)
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) {
            return new int[0];
        }
        int left = 0, right = matrix[0].length - 1, up = 0, down = matrix.length - 1;
        int num = (right + 1) * (down + 1);
        int[] nums = new int[num];
        int n = 0;
        while (true) {
            //遍历完第一行 up++
            for (int i = left; i <= right; i++) {
                nums[n++] = matrix[up][i];
            }
            up++;
            if (up > down) {
                break;
            }
            //遍历完最后一列 right--
            for (int i = up; i <= down; i++) {
                nums[n++] = matrix[i][right];
            }
            right--;
            if (right < left) {
                break;
            }
            //遍历完最后一行 down--
            for (int i = right; i >= left; i--) {
                nums[n++] = matrix[down][i];
            }
            down--;
            if (down < up) {
                break;
            }
            //遍历完第一列 left++
            for (int i = down; i >= up; i--) {
                nums[n++] = matrix[i][left];
            }
            left++;
            if (left > right) {
                break;
            }
        }
        return nums;
    }
}


剑指 Offer 66. 构建乘积数组【中等】


题目链接:剑指 Offer 66. 构建乘积数组


题目内容:给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。


思路:


1、乘积数组


复杂度分析:时间复杂度O(n),空间复杂度O(1),(数组 bb 作为返回值,不计入复杂度考虑)。


class Solution {
    //1 2 3 4 5
    //1 1 2 6 24 => 120 60 40 30 24,中间tmp作为乘数不断构建
    //核心不能够使用除法
    public int[] constructArr(int[] a) {
        if (a == null || a.length == 0) {
            return new int[0];
        }
        int[] b = new int[a.length];
        b[0] = 1;
        for (int i = 1; i < a.length; i++) {
            b[i] = b[i - 1] * a[i - 1];
        }
        int tmp = 1;
        for (int i = a.length - 2; i >= 0; i--) {
            tmp *= a[i + 1];
            b[i] = b[i] * tmp;
        }
        return b;
    }
}


牛客网


螺旋矩阵【简单】


题目链接:螺旋矩阵


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


思路1:定义四个方向,left、right、up、down,然后不同方向进行遍历即可统一添加到list中。


复杂度分析:


时间复杂度:O(n)
空间复杂度:O(n)
import java.util.ArrayList;
public class Solution {
    //  1 2 3
    //  4 5 6
    //  7 8 9   //1236987456
    public ArrayList<Integer> spiralOrder(int[][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
        if (matrix == null || 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;
    }
}


LRU最近最少使用算法031【中等】


题目:设计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
提示:
1.某个key的set或get操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过capacity时,移除最不经常使用的记录。
3.返回的value都以字符串形式表达,如果是set,则会输出"null"来表示(不需要用户返回,系统会自动输出),方便观察
4.函数set和get必须以O(1)的方式运行
5.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹


思路:


定义数据结构:双向链表以及一个哈希表
当前类需要的参数:一个头部结点、一个尾部结点、一个容量大小
核心两个方法:set()、get()方法
  set():设置链表结点,①从缓存中取。②若是缓存中有,修改哈希表中的结点、移动结点到头部。③若是缓存中没有,判断新增一个是否超出容量大小,若没有超出,那么直接添加到链表头部;若是超出了,删除尾部结点,将新节点添加到头部。
  get():从缓存中获取结点,若是没有直接返回null;若是有将结点移动至头部即可后返回。



题解:


/**
 * @ClassName LRUCache
 * @Author ChangLu
 * @Date 4/21/2022 5:38 PM
 * @Description 最近最少使用LRU
 * 核心思路:①设置指定的容量。②hash存储缓存键值对。③链表来存储一个读取的顺序。(最新读的、修改的、创建的会放在head;新增容量满的话就会移除最后节点也就是最近最少读取的)
 */
public class Solution {
    class DLinkedNode{
        int key;
        int value;
        //定义一个前后指针
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode(){}
        public DLinkedNode(int _key, int _val) {
            this.key = _key;
            this.value = _val;
        }
    }
    //缓存
    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    private int size;//当前的结点数量
    private int capacity;//容量大小
    //设置一个头尾结点
    private DLinkedNode head, tail;
    public Solution(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        this.head = new DLinkedNode();
        this.tail = new DLinkedNode();
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }
    public int get(int key) {
        //从缓存中取出节点
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        //todo 实现函数
        moveToHead(node);//将结点移到链表的头部
        //返回值
        return node.value;
    }
    public void set(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            //插入该值
            DLinkedNode newNode = new DLinkedNode(key, value);
            //出现缓存已满情况
            if ((cache.size() + 1) > capacity) {
                //todo 移除底部的元素
                DLinkedNode delNode = removeTail();
                cache.remove(delNode.key);
            }
            //更新哈希表
            cache.put(key, newNode);
            //更新双向链表
            addToHead(newNode);
        }else {
            //修改该值,同样将其添加到顶部
            node.value = value;
            //todo 将结点添加到head头部
            moveToHead(node);
        }
    }
    //将结点移动至头部
    public void moveToHead(DLinkedNode node) {
        //删除该节点的指向
        removeNode(node);
        //最新结点的指向
        addToHead(node);
    }
    //将结点添加到头部
    public void addToHead(DLinkedNode node) {
        node.next = head.next;
        node.prev = head;
        head.next = node;
        node.next.prev = node;
    }
    //移除尾部结点
    public DLinkedNode removeTail() {
        DLinkedNode tailNode = tail.prev;
        removeNode(tailNode);
        return tailNode;
    }
    public void removeNode(DLinkedNode node){ 
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}



设计LFU缓存结构【中等】


题目链接:设计LFU缓存结构


题目描述:每次移除最少使用的频率次数结点,若是频率相同,就再比较进入的先后



一个缓存结构需要实现如下功能。
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出
数据范围:0 < k \le 10^50<k≤10 
5
 ,|val| \le 2 \times 10^9∣val∣≤2×10 
9
要求:get和set的时间复杂度都是 O(logn)O(logn),空间复杂度是 O(n)O(n)


思路1:


设计数据结构:
  一个Node类包含key、value、freq;
  设计两个哈希表:
  第一个是结点表`Map<int, Node>`(key表示结点的key)
  第二个是频率表`Map<int, LinkedList<Node>>`(int是频率,LinkedList中维护了对应的结点记录)
维护一个class变量minFreq:最小频率
整体思路:LFU核心思路就是淘汰最近最少频率使用的元素。


题解:



class LFUCache {
    class Node {
        int key;
        int value;
        int freq;
        public Node(){}
        public Node(int key, int value, int freq){
            this.key = key;
            this.value = value;
            this.freq = freq;
        }
    }
    //定义一个节点表
    private Map<Integer, Node> nodeTable = new HashMap<>();
    //定义一个频率表
    private Map<Integer, LinkedList<Node>> freqTable = new HashMap<>();
    //最小频率
    int minFreq;
    //可插入结点的容量数
    int capacity;
    public LFUCache(int capacity) {
        this.capacity = capacity;
    }
    public int get(int key) {
        //边界问题
        if (capacity == 0) {
            return -1;
        }
        //从缓存中获取节点
        Node node = nodeTable.get(key);
        //情况1:没有获取到
        if (node == null) {
            return -1;
        }
        //情况2:获取到了,此时就需要进行一次更新操作
        int nodeVal = node.value;
        //todo 更新频率
        updateFreq(node, key, nodeVal);
        return nodeVal;
    }
    public void put(int key, int value) {
        //边界问题
        if (capacity == 0) {
            return;
        }
        //从缓存中获取结点
        Node node = nodeTable.get(key);
        //情况1:获取到了,更新频率表 + 更新结点表
        if (node != null) {
            updateFreq(node, key, value);
            return;
        }
        //情况2:没有获取到,说明当前没有该结点,需要进行插入结点
        //判断当前是否可以插入到节点,若是插入不了结点需要进行移除最小频率的最少使用结点
        if ((nodeTable.size() + 1) > capacity) {
            //删除最小频率以及最少使用的节点
            Node removeNode = freqTable.get(minFreq).removeLast();
            //删除结点表中的节点
            nodeTable.remove(removeNode.key);
        }
        //进行新增操作
        minFreq = 1;
        //频率表新增
        LinkedList<Node> list = freqTable.getOrDefault(minFreq, new LinkedList<Node>());
        Node newNode = new Node(key, value, minFreq);
        list.addFirst(newNode);
        if (freqTable.get(minFreq) == null) {
            freqTable.put(minFreq, list);
        }
        //节点表新增
        nodeTable.put(key, newNode);
    }
    public void updateFreq(Node node, int key, int value) {
        //更新结点表
        node.value = value;
        //更新评率表
        int nodeFreq = node.freq;//当前待更新结点的频率
        LinkedList oldList = freqTable.get(nodeFreq);
        oldList.remove(node);//删除频率表中原先的结点
        //1、更新结点的频率信息
        int newNodeFreq = nodeFreq + 1;
        //更新最小频率(条件:①最小频率=节点频率。②结点频率的链表节点树为0。)
        if (minFreq == nodeFreq && oldList.size() == 0) {
            freqTable.remove(minFreq);
            minFreq = newNodeFreq;
        }
        node.freq = newNodeFreq;
        LinkedList nodeList = freqTable.getOrDefault(newNodeFreq, new LinkedList());
        nodeList.addFirst(node);
        if (freqTable.get(newNodeFreq) == null) {
            freqTable.put(newNodeFreq, nodeList);
        }
    }
}



旋转数组【中等】


题目链接:旋转数组


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


思路1:三次反转。


第一次反转整个数组。第二次反转[0,m]位置。第三次反转[m, n - 1]位置,也就是后面剩余部分位置。

复杂度分析:


时间复杂度:O(n)
空间复杂度:O(1)
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) {
        m = m % n;//针对于超过a数组长度的情况
        //三次反转
        //6,2,123456 => 654321
        reverse(a, 0, n - 1);
        //654321 => 564321
        reverse(a, 0, m - 1);
        //564321 => 561234
        reverse(a, m, n - 1);
        return a;
    }
    //反转[i,j]的数组元素
    public void reverse(int[] a, int i, int j) {
        while (i < j) {
            int temp = a[i];
            a[i] = a[j];
            a[j] = temp;
            i++;
            j--;
        }
    }
}



顺时针旋转矩阵【中等】


题目链接:顺时针旋转矩阵


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


思路1:开辟新的数组,然后找规律即可。


复杂度分析:


时间复杂度:O(n2)
空间复杂度:O(n2)
import java.util.*;
public class Solution {
    public int[][] rotateMatrix(int[][] mat, int n) {
        int[][] temp = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                //画个图找规律即可
                temp[j][n - i - 1] = mat[i][j];
            }
        }
        return temp;
    }
}


思路2:直接在原始数组上来进行旋转,需要进行两次反转。



最后每行进行翻转:



复杂度分析:


时间复杂度:O(n2)
空间复杂度:O(1)
import java.util.*;
public class Solution {
    public int[][] rotateMatrix(int[][] mat, int n) {
        //转换两遍
        //第一遍,对称交换
        for (int i = 0; i < n; 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 < n; i++) {
            //写法1:
//             for (int j = 0; j < n / 2; j++) {
//                 int temp = mat[i][j];
//                 mat[i][j] = mat[i][n - j - 1];
//                 mat[i][n - j - 1] = temp;
//             }
            //写法2:翻转数组
            reverse(mat[i]);
        }
        return mat;
    }
    public void reverse(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
            left++;
            right--;
        }
    }
}
相关文章
|
6月前
|
存储 算法 C语言
【数据结构与算法 刷题系列】合并两个有序链表
【数据结构与算法 刷题系列】合并两个有序链表
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
21 0
|
4月前
【刷题记录】最大公因数,最小公倍数(辗转相除法、欧几里得算法)
【刷题记录】最大公因数,最小公倍数(辗转相除法、欧几里得算法)
|
4月前
|
算法 Python
【Leetcode刷题Python】改进的算法,高效求一个数的因子
一个高效的Python函数用于找出一个整数的所有因子,通过仅遍历到该数平方根的范围来优化性能。
47 0
|
6月前
|
算法
【数据结构与算法 刷题系列】求带环链表的入环节点(图文详解)
【数据结构与算法 刷题系列】求带环链表的入环节点(图文详解)
|
6月前
|
算法
【数据结构与算法 刷题系列】判断链表是否有环(图文详解)
【数据结构与算法 刷题系列】判断链表是否有环(图文详解)
|
6月前
|
算法
【数据结构与算法 刷题系列】移除链表元素
【数据结构与算法 刷题系列】移除链表元素
|
6月前
|
存储 算法 C语言
【数据结构与算法 刷题系列】环形链表的约瑟夫问题
【数据结构与算法 刷题系列】环形链表的约瑟夫问题
|
6月前
|
算法 C语言
【数据结构与算法 刷题系列】求链表的中间结点
【数据结构与算法 刷题系列】求链表的中间结点
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
92 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
下一篇
DataWorks