算法闭关修炼百题计划(六)

塔塔开(滑稽

  • 1.删除排序链表中的重复元素
  • 2.删除排序链表中的重复元素II
  • 3.字典序的第k小数字
  • 4.下一个排列
  • 5.排序链表
  • 6.随机链表的复制
  • 7.数据流的中位数

1.删除排序链表中的重复元素

使每个元素就出现一次

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) return nullptr;
        
        ListNode* cur = head;
        
        while (cur && cur->next) {
            if (cur->val == cur->next->val) {
                // Skip over duplicate nodes
                ListNode* temp = cur->next;
                cur->next = cur->next->next;
                delete temp; // Free memory if needed
            } else {
                // Move to the next distinct node
                cur = cur->next;
            }
        }
        
        return head;
    }
};

2.删除排序链表中的重复元素II

重复的全删了,一个也不留

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* cur = dummy;
        while(cur && cur->next && cur->next->next){
            //先把这个值存下来
            int num = cur->next->val;
            if(cur->next->next->val == num){
                //可能不止2个
                while(cur->next && cur->next->val == num){
                    cur->next = cur->next->next;
                }
            }
            else cur = cur->next;
        }
        return dummy->next;
    }
};

以上两题放在一起是故意的,学一学这个这个写法

3.字典序的第k小数字

给定整数 n 和 k,返回 [1, n] 中字典序第 k 小的数字。

输入: n = 13, k = 2
输出: 10
解释: 字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。

字节考频top10
字典序答,什么是字典序?
简而言之,就是根据数字的前缀进行排序,比如 10 < 9,因为 10 的前缀是 1,比 9 小。再比如 112 < 12,因为 112 的前缀 11 小于 12。这样排序下来,会跟平常的升序排序会有非常大的不同。先给你一个直观的感受,一个数乘 10,或者加 1,哪个大?可能你会吃惊,后者会更大。
在这里插入图片描述

class Solution {
public:
    int findKthNumber(int n, int k) {
        int curr = 1; // 从字典序第一个数字1开始
        k--;          // 减1是因为我们已经在第一个位置(数字1)

        while (k > 0) {
            int steps = calculateSteps(n, curr, curr + 1);
            if (steps <= k) {
                // k在当前前缀范围之外,跳到下一个前缀
                curr += 1;
                k -= steps;
            } else {
                // k在当前前缀范围之内,向下移动到更小的前缀
                curr *= 10;
                k -= 1;
            }
        }
        return curr;
    }

private:
    int calculateSteps(int n, long curr, long next) {
        int steps = 0;
        while (curr <= n) {
            steps += std::min((long)n + 1, next) - curr;
            curr *= 10;
            next *= 10;
        }
        return steps;
    }
};

findKthNumber:找到字典序第 k 小的数字。

从 curr = 1 开始,每次根据剩余的 k 确定是向下移动还是进入下一个数字前缀。
当 k == 0 时,curr 就是第 k 小的数字。
calculateSteps:计算以 curr 为前缀的子树节点数量。

curr 和 next 分别代表当前前缀和下一个前缀的起始数字,通过扩大 10 倍逐层计算包含的节点数。

4.下一个排列

整数数组的下一个排列是指其整数的下一个字典序更大的排列
arr = [1,2,3]的下一个排列是[1,3,2]。

给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。

现在给一个序列5 2 4 3 1,找下一个字典序排列,可以按照以下步骤:
在这里插入图片描述

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();
        int i = n - 2;

        // Step 1: Find the first decreasing element from the end
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // Step 2: If we found a valid i, find the next largest element to swap with
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) {
                j--;
            }
            swap(nums[i], nums[j]);
        }

        // Step 3: Reverse the decreasing sequence to get the smallest lexicographical order
        reverse(nums.begin() + i + 1, nums.end());
    }
};

step3中,笃定了nums[i]的右侧一定是降序的,所以总结reverse成升序,为什么这么笃定呢?
因为我们一开始找i,找的就是逆序数,找到了i,就意味着i的右侧都是顺序数,也就是降序
现在交换了nums[i]和一个比nums[i]大的最小数,右侧排序仍然是降序的,不会有任何改变!

5.排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

要对链表进行排序,可以使用归并排序,因为归并排序在链表上非常高效,且可以在O(nlogn)时间复杂度内完成
归并排序的思路是将链表递归拆成两半,对每一半进行排序,然后将它们合并。具体步骤如下:

  1. 使用快慢指针找到链表的中间节点,以便将链表分成两半。
  2. 递归对左右两半分别进行排序。
  3. 使用合并两个有序链表的方法将两半合并成一个有序链表。
class Solution {
public:
    ListNode* sortList(ListNode* head) {
    	// Base case: If the list is empty or has only one node, it's already sorted
        if(!head || !head->next) return head;
        
        // Step 1: Use fast and slow pointers to find the middle of the list
        ListNode* fast = head;
        ListNode* slow = head;
        ListNode* pre = nullptr;
        while(fast && fast->next){
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        
        // Cut the list into two halves
        pre->next = nullptr;
        
        // Step 2: Recursively sort each half
        ListNode* left = sortList(head);
        ListNode* right = sortList(slow);

		 // Step 3: Merge the sorted halves
        return merge(left, right);
    }
    ListNode* merge(ListNode* l1, ListNode* l2){
        ListNode* dummy = new ListNode(0);
        ListNode* tail = dummy;
        while(l1 && l2){
            if(l1->val < l2->val){
                tail->next = l1;
                l1 = l1->next;
            }else{
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }
        // If there are remaining elements in either list, append them
        if(l1){
            tail->next = l1;
        }else{
            tail->next = l2;
        }
        return dummy->next;
    }
};

6.随机链表的复制

在这里插入图片描述
在这里插入图片描述

题意解读:
创建一个新链表 链表的所有东西 val next random都要与原链表完全相同 但是node的地址不能和原链表相同 也就是进行深拷贝 类似c++类中有指针 要自定义拷贝构造函数

分析:
要复制一个带有随机指针的链表,可以分成几个步骤来完成。这里采用一种O(n)的时间复杂度和O(1)的空间复杂度的方法。这个方法的核心是将链表的节点复制一份并插入到原链表中,然后调整随机指针,最后将原链表和复制的链表分离。

思路:
第一步:在原链表中插入新节点
遍历原链表的每个节点,对每个节点创建一个新节点,并将新节点插入到原节点的后面。如果原链表是A->B->C,插入新节点后变成A->A’->B->B’->C->C’。
第二步:设置新节点的随机指针
遍历链表,对每个新节点设置其random指针。由于新节点在原节点的后面,因此node->next->random应该指向node->random->next(如果 node->random 不为空的话)。
上一句解释:
在这里插入图片描述
举例:
假设原链表node1->node2->node3
node1->random = node3
node2->random = node1
node3->random = node2

通过第一步,我们应该将复制的节点node1’,node2’,node3’加上去,结果如下
node1->node1’->node2->node2’->node3->node3’
现在我们需要更改random指针,比如node1’,我们需要他的random指针指向node3’而不是node3
所以更改random指针这一步是node->next->random = node->random->next!!!
第三步:将原链表和复制链表分离
再次遍历链表,将新节点从原链表中分离出来形成新的链表

在这个问题中,深拷贝的知识点是指创建一个完全独立的复制链表,其中每个节点都是原链表中节点的独立副本。具体来说,原链表中的每个节点不仅要复制他的值和next指针,还要复制他的random指针(指向链表中的任意节点或空指针)。而且,复制链表中的节点不能引用到原链表中的节点,必须是完全独立的。

独立的新节点:深拷贝会创建新的节点对象,而不是直接引用原节点对象。这意味着复制链表中的每个节点在内存中是独立的,与原链表中的节点没有共享部分。即使原链表被修改,复制链表的内容也不会受到影响。

复制复杂结构:对于一个有复杂指针结构(例如 random 指针)的数据结构,深拷贝要求复制每一个指针引用关系。在这个链表中,除了复制 next 指针,还要确保 random 指针也复制到新链表中相应位置。

避免浅拷贝问题:浅拷贝只会简单复制原链表节点的指针,使新链表中的节点指向原链表中的节点。这会导致原链表和复制链表共享相同的节点对象,若修改一个链表中的节点,另一个链表也会受到影响。在这个题目中,我们需要完全独立的复制链表,避免这样的副作用。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (!head) return nullptr;

        // Step 1: 创建新节点并插入到原节点后面
        Node* curr = head;
        while (curr) {
            Node* newNode = new Node(curr->val);
            newNode->next = curr->next;
            curr->next = newNode;
            curr = newNode->next;
        }

        // Step 2: 设置新节点的 random 指针
        curr = head;
        while (curr) {
            if (curr->random) {
                curr->next->random = curr->random->next;
            }
            curr = curr->next->next;
        }

        // Step 3: 将原链表和复制链表分离,注意!!不要破坏原链表!!
        curr = head;
        Node* newHead = head->next;
        Node* copy = newHead;
        while (curr) {
            curr->next = curr->next->next;
            if (copy->next) {
                copy->next = copy->next->next;
            }
            curr = curr->next;
            copy = copy->next;
        }

        return newHead;
    }
};

7.数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

例如 arr = [2,3,4] 的中位数是 3 。
例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。

实现 MedianFinder 类:
MedianFinder() 初始化 MedianFinder 对象。
void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10^-5 以内的答案将被接受。

输入
[“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

第一个数组是操作顺序,第二个数组是操作对应的数字

本题的核心思路是使用两个堆,也就是两个优先队列
小的倒三角就是个大顶堆,梯形就是个小顶堆,中位数可以通过它们的堆顶元素算出来:
在这里插入图片描述
addNum(int num)函数,当添加一个数时,首先判断该数应当放入哪个堆(大根堆或小根堆)。根据当前堆的大小,调整堆的平衡,确保两个堆的元素数目差不超过1。

findMedian()如果两个堆的大小相等,返回两个堆顶元素的平均值,如果堆的大小不等,直接返回大根堆的堆顶元素,因为它代表了数据流的中位数

class MedianFinder {
private:
    priority_queue<int> maxHeap; // 大根堆,用于存储较小的一半
    priority_queue<int, vector<int>, greater<int>> minHeap; // 小根堆,用于存储较大的一半

public:
    /** Initialize your data structure here. */
    MedianFinder() {}

    /** Adds a number into the data structure. */
    void addNum(int num) {
        // 保证maxHeap存储较小部分,minHeap存储较大部分
        if (maxHeap.empty() || num <= maxHeap.top()) {
            maxHeap.push(num);  // 如果num小于等于maxHeap的堆顶,加入maxHeap
        } else {
            minHeap.push(num);  // 否则加入minHeap
        }

        // 平衡两个堆,使得maxHeap和minHeap的大小差不超过1
        if (maxHeap.size() > minHeap.size() + 1) {
            minHeap.push(maxHeap.top());
            maxHeap.pop();
        } else if (minHeap.size() > maxHeap.size()) {
            maxHeap.push(minHeap.top());
            minHeap.pop();
        }
    }

    /** Returns the median of current data stream */
    double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.top() + minHeap.top()) / 2.0; // 两个堆大小相等,取两个堆顶的平均值
        } else {
            return maxHeap.top(); // 如果maxHeap多一个元素,返回maxHeap的堆顶元素
        }
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915286.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PH热榜 | 2024-11-13

DevNow 是一个精简的开源技术博客项目模版&#xff0c;支持 Vercel 一键部署&#xff0c;支持评论、搜索等功能&#xff0c;欢迎大家体验。 在线预览 1. Agree.com 标语&#xff1a;人人免费电子签名&#xff01; 介绍&#xff1a;Agree&#xff0c;这款由人工智能驱动的平台…

PTE-中间件安全

DOCKER环境&#xff0c;一般是80 8080 8081端口 1 apache位置扩展名解析漏洞 cd vulhub-master/httpd/apache_parsing_vulnerability/ docker-compose up -d 修改一句话的后缀 直接上传 蚁剑 2 CVE-2017-15715 docker-compose stop cd .. cd CVE-2017-15715/ dock…

【Linux】Github 仓库克隆速度慢/无法克隆的一种解决方法,利用 Gitee 克隆 Github 仓库

Github 经常由于 DNS 域名污染以及其他因素克隆不顺利。 一种办法是修改 hosts sudo gedit /etc/hosts加上一行 XXX.XXX.XXX.XXX github.comXXX 位置的 IP 可以通过网站查询 IP/服务器github.com的信息-站长工具 这种方法比较适合本身可以克隆&#xff0c;但是速度很慢的…

Elasticsearch 8.16:适用于生产的混合对话搜索和创新的向量数据量化,其性能优于乘积量化 (PQ)

作者&#xff1a;来自 Elastic Ranjana Devaji, Dana Juratoni Elasticsearch 8.16 引入了 BBQ&#xff08;Better Binary Quantization - 更好的二进制量化&#xff09;—— 一种压缩向量化数据的创新方法&#xff0c;其性能优于传统方法&#xff0c;例如乘积量化 (Product Qu…

MySQL数据库常用命令大全(完整版——表格形式)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

微型导轨在自动化生产线中起什么作用?

在现代制造业的飞速跃进中&#xff0c;自动化生产线的蓬勃发展引领了一场效率与质量的双重革命。微型导轨作为传动领域的重要零部件&#xff0c;可用于工业自动化生产线上的零件运输、加工设备定位等&#xff0c;实现自动化生产和减少人力成本。那么&#xff0c;微型导轨在自动…

Flutter 小技巧之 Shader 实现酷炫的粒子动画

在之前的《不一样的思路实现炫酷 3D 翻页折叠动画》我们其实介绍过&#xff1a;如何使用 Shader 去实现一个 3D 的翻页效果&#xff0c;具体就是使用 Flutter 在 3.7 开始提供 Fragment Shader API &#xff0c;因为每个像素都会过 Fragment Shader &#xff0c;所以我们可以通…

c++实现B树(下)

书接上回小吉讲的是B树的搭建和新增方法的实现&#xff08;blog传送门&#x1f6aa;&#xff1a;B树实现上&#xff09;&#xff08;如果有小可爱对B树还不是很了解的话&#xff0c;可以先看完上一篇blog&#xff0c;再来看小吉的这篇blog&#xff09;。那这一篇主要讲的是B树中…

【Oracle篇】掌握SQL Tuning Advisor优化工具:从工具使用到SQL优化的全方位指南(第六篇,总共七篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

使用Java绘制图片边框,解决微信小程序map组件中marker与label层级关系问题,label增加外边框后显示不能置与marker上面

今天上线的时候发现系统不同显示好像不一样&#xff0c;苹果手机打开的时候是正常的&#xff0c;但是一旦用安卓手机打开就会出现label不置顶的情况。尝试了很多种办法&#xff0c;也在官方查看了map相关的文档&#xff0c;发现并没有给label设置zIndex的属性&#xff0c;只看到…

关于sass在Vue3中编写bem框架报错以及警告问题记录

在编写完bem框架后 在vite.config.ts文件进行预编译处理时&#xff0c;报错的错误 1. 处理方式&#xff1a;使用新版api&#xff0c; 如图&#xff1a; 2. 处理方式&#xff1a;使用 use 替换掉 import&#xff0c; 如图&#xff1a; 3. 处理方式&#xff1a;使用路径别名&am…

BizDevOps:从理念到实践,贯通企业全链路协同

&#x1f446; 点击蓝字 关注我们 引言 BizDevOps的概念由DevOps发展和进化而来&#xff0c;其目标超越了开发和运维的协同&#xff0c;进一步实现业务、研发和运维的全链条协作&#xff0c;让业务作为价值的起点及核心目标。 BizDevOps的核心驱动力在于解决效率和正确性上的割…

C#与C++交互开发系列(二十二):跨进程通信之使用基于HTTP协议的REST风格的API

1. 前言 REST API&#xff08;Representational State Transfer Application Programming Interface&#xff09;是一种基于HTTP协议的通信方式&#xff0c;广泛用于网络服务和分布式应用程序之间的通信。通过REST API&#xff0c;可以让C#和C应用程序进行跨进程、甚至跨平台的…

ECharts饼图-饼图15,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个饼图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供详…

Python在数据科学中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Python在数据科学中的应用 Python在数据科学中的应用 Python在数据科学中的应用 引言 Python 概述 定义与特点 发展历程 Python…

IDEA2024:右下角显示内存

使用场景&#xff1a; 实时知晓idea内存使用情况 解决方案: 开启内存显示 View -> Apperance -> Status Bar Widgets -> Memory Indicator 效果如下&#xff1a;

【计算机网络】【网络层】【习题】

计算机网络-传输层-习题 文章目录 13. 图 4-69 给出了距离-向量协议工作过程&#xff0c;表&#xff08;a&#xff09;是路由表 R1 初始的路由表&#xff0c;表&#xff08;b&#xff09;是相邻路由器 R2 传送来的路由表。请写出 R1 更新后的路由表&#xff08;c&#xff09;。…

vue 计算属性get set

<template><div id"app"><h1>用户信息</h1><p>全名&#xff1a;{{ fullName }}</p><input v-model"fullName" placeholder"请输入全名" /><p>姓&#xff1a;{{ firstName }}</p><p>…

74HC245

74HC245&#xff1a;典型的CMOS型缓冲门电路 在这里用于增加电压

【代码管理之道】Git 高级工作流与团队协作实践:深入探讨与实战案例

引言 在前几篇文章中&#xff0c;我们详细介绍了 Git 的基本概念、高级功能、最佳实践以及高级工作流和团队协作实践。本文将继续深入探讨 Git 的高级工作流和团队协作实践&#xff0c;帮助读者更好地理解和应用这些概念。我们将通过具体的实战案例&#xff0c;展示如何在实际…