手撕算法——顺序表

算法基础——顺序表-优快云博客

一、询问学号

题⽬来源:洛⾕

题⽬链接:P3156 【深基15.例1】询问学号 - 洛谷

难度系数:★

1. 题目描述

2. 算法原理

直接⽤ vector 或者数组模拟即可。

3. 参考代码

#include <iostream>
#include <vector>

using namespace std;

const int N = 2e6 + 10;

int n, m;
vector<int> a(N);

int main()
{
    cin >> n >> m;

    for(int i = 1; i <= n; i++) cin >> a[i];

    while(m--)
    {
        int x; cin >> x;
        cout << a[x] << endl;
    }

    return 0;
}

二、寄包柜

题⽬来源:洛⾕

题⽬链接:P3613 【深基15.例2】寄包柜 - 洛谷

难度系数:★

1. 题目描述

2. 算法原理

解法1:创建二维数组(空间复杂度过高)

解法2:使用vector——vector<int> a[N]

注意

  • vector<int> a[N] 定义了一个包含 N 个 vector<int> 对象的数组。(类似二维数组)
  • vector<int> a(N) 定义了一个 vector<int> 对象 a,并将其初始化为包含 N 个元素,这些元素的初始值默认为 0(对于 int 类型)。

3. 参考代码

#include <iostream>
#include <vector>

using namespace std;

const int N = 1e5 + 10;

int n, q;
vector<int> a[N]; // 创建 N 个柜子

int main()
{
    cin >> n >> q;

    while(q--)
    {
        int op, i, j, k;
        cin >> op >> i >> j;

        if(op == 1) // 存
        {
            cin >> k;
            if(a[i].size() <= j)
            {
                // 扩容
                a[i].resize(j + 1);
            }

            a[i][j] = k;
        }
        else // 查询
        {
            cout << a[i][j] << endl;
        }
    }


    return 0;
}

三、移动零

题⽬来源:⼒扣

题⽬链接:283. 移动零 - 力扣(LeetCode)

难度系数:★

1. 题目描述 

2. 算法原理

利用双指针

类⽐数组分两块的算法思想,这⾥是将数组分成三块,那么我们可以再添加⼀个指针,实现数组分三 块。

设数组⼤⼩为 n ,定义三个指针 left, cur,right :

  • left :⽤来标记 0 序列的末尾,因此初始化为 -1 ;
  • cur⽤来扫描数组,初始化为 0 ; 
  • right :⽤来标记 2 序列的起始位置,因此初始化为 n  。

在往后扫描的过程中,保证:

  • [0, left] 内的元素都是 0 ;
  • [left + 1,cur − 1]内的元素都是 1 ; 
  • [cur, right − 1] 内的元素是待定元素;
  • [right, n] 内的元素都是 2 。

3. 参考代码

class Solution 
{
public:
    void moveZeroes(vector<int>& nums) 
    {
        for(int i = 0, cur = -1; i < nums.size(); i++)
        {
            if(nums[i]) // 非0元素
            {
                swap(nums[++cur], nums[i]);
            }
        }
    }
};

四、颜⾊分类

题⽬来源:⼒扣 

题⽬链接:75. 颜色分类 - 力扣(LeetCode)

难度系数:★

1. 题目描述

2. 算法原理

类⽐数组分两块的算法思想,这⾥是将数组分成三块,那么我们可以再添加⼀个指针,实现数组分三 块。

设数组⼤⼩为 ,定义三个指针left, cur,right :

  • left:⽤来标记 序列的末尾,因此初始化为 ;
  • cur:⽤来扫描数组,初始化为 ;
  • right:⽤来标记 序列的起始位置,因此初始化为 。

在 往后扫描的过程中,保证:

  • [0, left] 内的元素都是 ;
  • [left + 1,cur − 1] 内的元素都是 ;
  • [cur, right − 1] 内的元素是待定元素;
  • [right, n] 内的元素都是 。

  

3. 参考代码

class Solution 
{
public:
    void sortColors(vector<int>& nums) 
    {
        int left = -1, i = 0, right = nums.size();

        while(i < right)
        {
            if(nums[i] == 0) swap(nums[++left], nums[i++]);
            else if(nums[i] == 1) i++;
            else swap(nums[--right], nums[i]);
        }
    }
};

五、合并两个有序数组

题⽬来源:⼒扣

题⽬链接:88. 合并两个有序数组 - 力扣(LeetCode)

难度系数:★

1. 题目描述

2. 算法原理

解法⼀:利⽤辅助数组(需要学会,归并排序的核⼼步骤)

可以创建⼀个辅助数组,然后⽤两个指针分别指向两个数组。每次拿出⼀个较⼩的元素放在辅助数组 中,直到把所有元素全部放在辅助数组中。最后把辅助数组的结果覆盖到 nums1 中。

解法⼆:原地修改(本题的最优解)

与解法⼀的核⼼思想是⼀样的。

由于第⼀个数组的空间本来就是 n+m 个,所以我们可以直接把最终结果放在 nums1 中。 nums1 中。为了不 覆盖未遍历到的元素,定义两个指针指向两个数组的末尾,从后往前扫描。每次拿出较⼤的元素也是 从后往前放在 nums1 的后⾯,直到把所有元素全部放在 nums1 中。

注意:从前往后遍历会出现覆盖的情况

3. 参考代码

//解法一
class Solution 
{
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) 
    {
        // 解法一:利用辅助数组
        vector<int> tmp(m + n);

        int cur = 0, cur1 = 0, cur2 = 0;

        while(cur1 < m && cur2 < n)
        {
            if(nums1[cur1] <= nums2[cur2]) tmp[cur++] = nums1[cur1++];
            else tmp[cur++] = nums2[cur2++];
        }

        while(cur1 < m) tmp[cur++] = nums1[cur1++];
        while(cur2 < n) tmp[cur++] = nums2[cur2++];

        for(int i = 0; i < n + m; i++) nums1[i] = tmp[i];
    }
};


//解法二
class Solution 
{
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) 
    {
        // 解法二:原地合并
        int cur1 = m - 1, cur2 = n - 1, cur = m + n - 1;

        while(cur1 >= 0 && cur2 >= 0)
        {
            if(nums1[cur1] >= nums2[cur2]) nums1[cur--] = nums1[cur1--];
            else nums1[cur--] = nums2[cur2--];
        }

        while(cur2 >= 0) nums1[cur--] = nums2[cur2--];
    }
};

六、The Blocks Problem

题⽬来源:洛⾕

题⽬链接:UVA101 The Blocks Problem - 洛谷

难度系数:★★

2. 算法原理

本质是⼀个模拟题,可以⽤ vector 来模拟,注意细节问题。

3. 参考代码

#include <iostream>
#include <vector>

using namespace std;

const int N = 30;
typedef pair<int, int> PII;

int n;
vector<int> p[N]; // 创建 n 个放木块的槽

PII find(int x)
{
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < p[i].size(); j++)
        {
            if(p[i][j] == x)
            {
                return {i, j};
            }
        }
    }
}

void clean(int x, int y)
{
    // 把 [x, y] 以上的木块归位

    for(int j = y + 1; j < p[x].size(); j++)
    {
        int t = p[x][j];
        p[t].push_back(t);
    }
    p[x].resize(y + 1);
}

void move(int x1, int y1, int x2)
{
    // 把 [x1, y1] 及其以上的木块放在 x2 上面

    for(int j = y1; j < p[x1].size(); j++)
    {
        p[x2].push_back(p[x1][j]);
    }
    p[x1].resize(y1);
}

int main()
{
    cin >> n;
    // 初始化
    for(int i = 0; i < n; i++)
    {
        p[i].push_back(i);
    }

    string op1, op2;
    int a, b;

    while(cin >> op1 >> a >> op2 >> b)
    {
        // 查找 a 和 b 的位置
        PII pa = find(a);
        int x1 = pa.first, y1 = pa.second;
        PII pb = find(b);
        int x2 = pb.first, y2 = pb.second;

        if(x1 == x2) continue; // 处理不合法的操作

        if(op1 == "move") // 把 a 上方归位
        {
            clean(x1, y1);
        }
        if(op2 == "onto") // 把 b 上方归位
        {
            clean(x2, y2);
        }

        move(x1, y1, x2);
    }

    // 打印
    for(int i = 0; i < n; i++)
    {
        cout << i << ":";
        for(int j = 0; j < p[i].size(); j++)
        {
            cout << " " << p[i][j];
        }
        cout << endl;
    }

    return 0;
}

### 动实现LRU缓存算法 LRU(Least Recently Used,最近最少使用)是一种常见的缓存淘汰策略。其实现的核心在于维护一个能够快速查找和更新的数据结构组合——哈希表(Hash Table)用于存储键值对并提供 O(1) 的查找时间,而双向链表(Doubly Linked List)则用来记录访问顺序以便于高效地移动节点到头部或将尾部节点移除。 以下是基于 Python 实现的一个简单版本的 LRU 缓存: #### 使用字典与双向链表的动实现 ```python class Node: def __init__(self, key, value): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} # 哈希表用于映射键到节点 self.head = Node(0, 0) # 虚拟头结点 self.tail = Node(0, 0) # 虚拟尾结点 self.head.next = self.tail self.tail.prev = self.head def _add_node(self, node: Node): """将新节点添加到双向链表的头部""" node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def _remove_node(self, node: Node): """从双向链表中删除指定节点""" prev = node.prev next_ = node.next prev.next = next_ next_.prev = prev def _move_to_head(self, node: Node): """将某个已存在的节点移到链表头部""" self._remove_node(node) self._add_node(node) def _pop_tail(self) -> Node: """弹出链表中的最后一个实际节点(即最不常使用的节点)""" res = self.tail.prev self._remove_node(res) return res def get(self, key: int) -> int: """获取缓存中的值,如果存在,则将其标记为最近使用;否则返回 -1""" if key not in self.cache: return -1 node = self.cache[key] self._move_to_head(node) return node.value def put(self, key: int, value: int): """插入新的键值对,或者更新已有键的值,并调整其位置至最近使用""" if key in self.cache: node = self.cache[key] node.value = value self._move_to_head(node) else: new_node = Node(key, value) self._add_node(new_node) self.cache[key] = new_node if len(self.cache) > self.capacity: tail = self._pop_tail() del self.cache[tail.key] ``` 上述代码实现了 LRU 缓存的主要逻辑[^1]。`Node` 类表示双向链表中的单个节点,其中包含了 `key` 和 `value` 属性以及前后指针。`LRUCache` 利用了两个辅助方法 `_add_node` 和 `_remove_node` 来管理双向链表的操作,同时通过 `_move_to_head` 方法确保每次访问过的节点都被视为最近使用。 当容量超出限制时,会调用 `_pop_tail` 删除最久未使用的节点及其对应的哈希条目[^2]。 #### 使用 ES6 Map 的简化实现 另一种更简洁的方式可以利用 JavaScript 中的内置 `Map` 数据结构来完成相同的功能。由于 `Map` 默认保留了插入顺序,因此无需显式构建双向链表即可轻松模拟 LRU 行为[^3]: ```javascript class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); } get(key) { if (!this.map.has(key)) return -1; const value = this.map.get(key); this.map.delete(key); // 移除旧项 this.map.set(key, value); // 将该项重新加入以保持最新状态 return value; } put(key, value) { if (this.map.has(key)) { this.map.delete(key); // 如果已经存在该键,则先删除它 } this.map.set(key, value); if (this.map.size > this.capacity) { this.map.delete(this.map.keys().next().value); // 删除最早插入的一项 } } } ``` 此版本依赖于 `Map.prototype.keys()` 返回迭代器的能力自动跟踪最早的项目,从而减少了自定义链表的需求。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值