TypeScript算法题实战——剑指 Offer篇(3)

news2024/11/29 16:37:05

随着TypeScript的流行,越来越多的开发者开始使用TypeScript来解决算法问题。

在本文中,我们将使用TypeScript来解决剑指offer的算法题。这些问题涵盖了各种各样的主题,包括数组、字符串、链表、树、排序和搜索等。我们将使用TypeScript的强类型和面向对象的特性来解决这些问题,并通过实际的代码示例来演示如何使用TypeScript来解决算法问题。

题目全部来源于力扣题库:《剑指 Offer(第 2 版)》本章节包括的题目有:

题目难度
从上到下打印二叉树简单
二叉搜索树的后序遍历序列简单
二叉树中和为某一值的路径简单
字符串的排列中等
数组中出现次数超过一半的数字简单
最小的k个数中等
连续子数组的最大和中等
数字序列中某一位的数字中等
把数组排成最小的数中等
把数字翻译成字符串中等

一、从上到下打印二叉树

1.1、题目描述

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:

给定二叉树: [3,9,20,null,null,15,7],
在这里插入图片描述
返回: [3,9,20,15,7]

1.2、题解

使用队列的方法进行层次遍历,首先将根节点压入队列,然后每从队首出一个元素,就将该元素的左右子节点压入队尾,这样就可以实现层序遍历。

function levelOrder(root: TreeNode | null): number[] {
    let res:number[] = [];
    let que:TreeNode[] = [];

    if(!root)
        return res;
    que.push(root);
    while(que.length){
        let tmp: TreeNode = que.shift();
        if(tmp == null)
            continue;
        res.push(tmp.val);
        if(tmp.left)
            que.push(tmp.left);
        if(tmp.right)
            que.push(tmp.right);
    }
    return res;
};

二、 二叉搜索树的后序遍历序列

2.1、题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true

2.2、题解

题目多读几遍就可以理解,二叉搜索树的后序遍历结果其实也是部分有序的,二叉搜索树的特点是左子树的值<根节点<右子树的值。而后续遍历的顺序是:左子节点→右子节点→根节点,后续遍历的最后一个数字一定是根节点,所以数组中最后一个数字就是根节点,我们从前(第0个)往后找到第一个比根节点大的数字,然后从这里分段,其左边的都是左子树,右边就是右子树

这里需要判断一下右子树的内部情况,如果其中有小于根节点的,那说明不是二叉搜索树,直接返回false。然后再以递归的方式判断左右子树。

function verifyPostorder(postorder: number[]): boolean {
    if(postorder.length <= 1)
        return true;
    let root:number = postorder[postorder.length - 1];
    let index:number = 0;
    while(index < postorder.length - 1&&postorder[index] < root){
        index ++;
    }
    let leftChild:number[] = postorder.slice(0, index);
    let rightChild:number[] = postorder.slice(index, postorder.length - 1);
    for(let i = 0; i < rightChild.length; i++){
        if(rightChild[i] < root)
            return false;
    }
    return verifyPostorder(leftChild) && verifyPostorder(rightChild);
};

三、二叉树中和为某一值的路径

3.1、题目描述

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点

3.2、题解

使用深度优先搜索算法,val表示当前路径已经累计的和,list存储当前路径。
要注意的是:
1、不要根据路径和当前大小剪枝,因为题目里会有负数,只能全部遍历;
2、要是ans.push(list.slice())压入list的复制,如果是ans.push(list)压入list的引用的话,会影响res

function pathSum(root: TreeNode | null, target: number): number[][] {
    let ans:number[][] = [];
    let t:number = target;
    function dfs(root: TreeNode | null, val, list): void{
        if(root == null)
            return;
        list.push(root.val);
        if(root.val + val == t && root.left == null && root.right == null){
            ans.push(list.slice());
        }
        dfs(root.left, val + root.val, list);
        dfs(root.right, val + root.val, list);
        list.pop();
    }
    dfs(root, 0, []);
    return ans;
};

四、字符串的排列

4.1、题目描述

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

4.2、题解

使用哈希表+DFS方法,pos代表当前的深度位置,让当前位置与之后所有位置进行交换,catSet是一个哈希集合,用于排除重复的方案,char记录当前的的排列,当pos == s.length时,当次递归结束,将char处理后压入res中即可。

function permutation(s: string): string[] {
    let char = s.split('');
    let res:string[] = [];
    function dfs(pos){
        if(pos == s.length){
            res.push(char.join(""));
            return;
        }
        let catSet = new Set();
        for(let i = pos; i < s.length; i++){
            if(catSet.has(char[i])){
                continue;
            }
            catSet.add(char[i]);
            {
                const tmp = char[pos];
                char[pos] = char[i];
                char[i] = tmp; 
            }
            dfs(pos + 1);
             {
                const tmp = char[pos];
                char[pos] = char[i];
                char[i] = tmp; 
            }
        }
    }
    dfs(0);
    return  res;
};

五、数组中出现次数超过一半的数字

5.1、题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

5.2、题解

使用Boyer-Moore 投票算法也叫摩尔投票法,维护一个候选众数 candidate 和它出现的次数 count

  1. 如果 xcandidate 相等,那么计数器 count 的值增加 1;
  2. 如果 xcandidate 不等,那么计数器 count 的值减少 1。
  3. . 如果 xcandidate 不等,且计数器再减1后小于0了,那么说明当前疑似的众数被减光了,换candidatecount置1。
function majorityElement(nums: number[]): number {
    let count:number = 0;
    let candidate: number = -1;
    for(let i = 0; i < nums.length; i++){
        if(nums[i] == candidate)
            ++ count;
        else{
            if(-- count < 0){
                count = 1;
                candidate = nums[i];
            }
        }
    }
    return candidate;
};

六、最小的k个数

6.1、题目描述

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

6.2、题解

解法一:调用sort排序后返回前k个元素,使用sort((a, b) =>{ return a - b})将数组从小到大排序,然后使用slice返回前k个。

function getLeastNumbers(arr: number[], k: number): number[] {
    return arr.sort((a, b) =>{ return a - b}).slice(0, k);
};

解法二:部分快排分治法

参考:https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/solution/chao-quan-3chong-jie-fa-zhi-jie-pai-xu-zui-da-dui-/
单次快排后,返回的index之前的所有元素均比arr[index]小,index之后的所有元素均比arr[index]大,那么

  1. 如果index等于k,即index之前的结果就是题解;
  2. 如果index小于k,即index之前的结果还不够k个,所以需要将index之后的结果继续快排;
  3. 如果index大于k,即index之前的结果多于k个,需要将index之前的结果再次快排。
function getLeastNumbers(arr: number[], k: number): number[] {
    if(k >= arr.length)
        return arr;
    let left:number = 0;
    let right:number = arr.length - 1; 
    // 快排的单次分治
    function partition(arr:number[], start:number, end:number):number {
        const k:number = arr[start];
        let left:number = start + 1;
        let right:number = end;

        while(1){
            while(left <= end && arr[left] <= k) ++left;
            while(right >= start + 1 && arr[right] >= k) --right;
            if(left >= right)
                break;

        [arr[left], arr[right]] = [arr[right], arr[left]];
        ++left;
        --right;
        }
        [arr[right], arr[start]] = [arr[start], arr[right]];
        return right;
    }
    //index为当前分治点,若点位小于k,则往右边继续分治,若点位大于k则往左边继续分治
    let index:number = partition(arr, left, right);
    while(index !== k){
        if(index < k){
            left = index + 1;
            index = partition(arr, left, right);
        }
        else if(index > k){
            right = index - 1;
            index = partition(arr, left, right);
        }
    }
    return arr.slice(0, k);
};

七、连续子数组的最大和

7.1、题目描述

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为6。

7.2、题解

使用动态规划,维护一个dp数组,dp[i]表示以元素nums[i]为结尾的连续子数组最大和,其中dp[i]=max((dp[i-1] + nums[i]), nums[i]),res用于保存dp中的最大值,可以边算状态数组时边记录最大值,也可以最后再遍历一次状态数组。

function maxSubArray(nums: number[]): number {
    let res:number = nums[0];
    let dp:number[] = [];
    dp[0] = res;
    for(let i = 1; i < nums.length; i++){
        dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
        res = Math.max(res, dp[i]);
    }
    return res;
};

八、数字序列中某一位的数字

8.1、题目描述

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0

8.2、题解

参考https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/js-5xing-dai-ma-ji-shi-xing-zhu-shi-by-o-2skd/

题目最后仅需要返回第n位对应的单个数字,但是每个数字的长度不同,会影响逻辑判断,那么我们可以扩充每个数字到相同的长度来计算位置。

例如要找到第15位置(应该返回122),我们扩充为 00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24 ....

跟原数列相比,第15位置其实前面增加了10个0位,此时其在第25位置。如何在这个新的序列中计算呢?首先找到是第几个隔间:Math.floor(25/2)=12,然后再找是该隔间的第几个元素,25%2=1,即为第12个隔间的第1个元素:2(下标从0开始)

而要找到第205位置,扩充为001|002|003|004|005|006|007|008|009|010|011|012|013|...

此时第205位置前面新增的位数为10 + 100,此时其在第315位置。然后以同样方法找到第几个隔间:Math.floor(315/3)=105,然后再找是该隔间的第几个元素,315%3=0,即为第105个隔间的第0个元素:1

以这种思路,代码如下:

function findNthDigit(n: number): number {
    let i = 1;
    while( i * (10 ** i) < n){
        n = n + 10 ** i;
        i ++;
    }
    return Number((Math.floor(n / i) + "")[n % i]);
};

九、把数组排成最小的数

9.1、题目描述

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:
输入: [10,2]
输出: “102”

示例 2:
输入: [3,30,34,5,9]
输出: “3033459”

9.2、题解

从两个数开始讲,比如3和30,可以排成3 + 30 => 33030 + 3 => 303,题目要求最小,那么采用30 + 3的方案,而在数组中,就可以把30排在3前面,以保证这两数的部分满足最小方案。

从三个数来看,3,30,15,首先可以看3和30,先排成30,3,15的模式,然后再继续往后看3,15,3和15可以排成3 + 15 => 31515 + 3 => 153,153更小,故可以排成15,3的模式,此时数组变成30,15,3,再来一遍,以此类推,最后数组变成了15,30,3,为最小情况。

推广到多个数,其实可以发现上述步骤在做一种冒泡排序操作,只是这种排序的条件是判断两数交换后的组合数是否更小,由于js和ts原生sort函数支持回调函数,我们直接修改回调函数,最后将排序好的数组内部元素转成字符串并连接起来即可。

function minNumber(nums: number[]): string {
    nums.sort((a, b) => Number(String(a) + String(b)) < Number(String(b) + String(a)) ? -1 : 1)
    return nums.map(e => String(e)).join('');
};

十、把数字翻译成字符串

10.1、题目描述

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:
输入: 12258 输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”,
“mcfi"和"mzi” 。

10.2、题解

分析本题,从12258开始分析,首先从头开始12258可以分为1/2258和12/258,这个问题就变成计算2258和258有多少种情况,然后将他们相加。

分析1/2258,其可以分为1/2/258和1/22/58,分析12/258,其可以分为12/2/58和12/25/8,依此类推,这个就很像做过的跳台阶问题,一次可以选择跳一级或者跳两级,只不过这里跳两级的条件要加上不能大于25。

基于这种思想,使用递归法,代码如下:

function translateNum(num: number): number {
    let str:string = num.toString();
    function dfs(str, point){
        if(point >= str.length - 1)
            return 1;
        const temp = Number(str[point] + str[point + 1]);
        if(temp >= 10 && temp <= 25){
            return dfs(str, point + 1) + dfs(str, point + 2);
        }else{
            return dfs(str, point + 1);
        }
    }

    return dfs(str, 0);
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL高级篇笔记 (中-索引的数据结构) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、索引及其优缺点 1、索引概述 2、优点 3、缺点 二、InnoDB中索引的推演 1、设计索引 1.一个简单的索引设计方案 2.InnoDB中的索引方案 2、常见索引概念 1. 聚簇索引 2. 二级索引&#xff08;辅助索引、非聚簇索引&#…

Node.js详解(一):基础知识

文章目录 一、Node.js介绍二、Node.js的优势三、Node.js的特点1、V8虚拟机2、事件驱动3、异步、非堵塞I/O 四、NodeJS带来的对系统瓶颈的解决方案1. 并发连接2. I/O阻塞 五、NodeJS的优缺点1、优点&#xff1a;2、缺点&#xff1a; 六、适合NodeJS的场景1、RESTful API2、统一W…

VMware、Ubuntu安装以及虚拟机复制粘贴问题

安装VMware 下载阿里云链接&#xff08;16 pro&#xff09;&#xff1a;VMware https://www.aliyundrive.com/s/ot9dhPNdSwC 安装&#xff1a;选一下安装地址&#xff0c;一直下一步即可。&#xff08;可能会要求重启电脑&#xff0c;重启即可&#xff09; 然后点击“许可证”…

Java 高级应用-多线程-(四)FutureTask的介绍及使用

Java多线程之FutureTask的介绍及使用 FutureTask属于java.util.concurrent 包&#xff1b;FutureTask表示可取消的异步计算。FutureTask类提供了一个Future的基本实现 &#xff0c;具有启动和取消计算的方法&#xff0c;查询计算是否完整&#xff0c;并检索计算结果。结果只能…

Camtasia2023试用版新功能介绍

Camtasia 2023在易用性更进一步&#xff0c;再一次降低了制作精美视频的门槛&#xff0c;下面看一看&#xff0c;Camtasia 2023有哪些的新功能&#xff01;包括影像、音效、鼠标移动轨迹、解说声音等等内容的录制&#xff0c;并且软件还可以提供即时播放和编辑压缩的功能&#…

如何监控电动车充电桩能耗?

一 背景 随着新能源汽车的快速发展&#xff0c;像特斯拉、BYD、蔚来、小鹏和理想等品牌的电动汽车在我们的日常生活中越来越多了&#xff0c;可见电动汽车如今已逐渐被我们所认可了。同汽油车需要加油一样&#xff0c;电动汽车需要充电&#xff0c;如此一来&#xff0c;电动汽…

第14届蓝桥杯省赛真题剖析-2023年5月7日Scratch编程中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第142讲。 第14届蓝桥杯Scratch省赛真题&#xff0c;这是2023年5月7日举办的省赛中级组试题&#xff0c;比赛仍然采取线…

洗地机充电底座语音芯片选型?NV040DS语音芯片

一、洗地机语音提示功能的价值 洗地机充电底座加入语音提示功能&#xff0c;主要是为了提高洗地机的智能化程度和使用便利性&#xff01; 1. 提高使用效率&#xff1a;底座语音提示充电状态可以使用户更方便地掌握底座电量和洗地机的使用情况&#xff0c;从而更快捷地对底座进…

ProtoBuf 语法(二)

系列文章 ProtoBuf 语法&#xff08;一&#xff09; ProtoBuf 语法&#xff08;三&#xff09; 文章目录 八、更新消息8.1 更新规则8.2 reserved 保留字段8.3 验证错误删除字段造成的数据损坏8.4 未知字段及其获取方法8.5 验证未知字段 八、更新消息 8.1 更新规则 如果现有的…

mysql中的count(1)、count(*)、count(id)哪个更快?

今天和大家聊一下mysql中的count()方法 我们日常开发中&#xff0c;经常会用到count()命令&#xff0c;有的人用count(*)&#xff0c;有的人用count(1)&#xff0c;还有的人用count(id)&#xff0c;那么这几种写法都有什么区别呢&#xff1f;哪种方法效率更高呢&#xff1f;今…

LangChain 查询使用指「北」

一只鹦鹉加上一根链条&#xff0c;组成了时下最流行的 AI 话题热门榜选手——LangChain。 LangChain 是一种 AI 代理工具&#xff0c;可以为以 ChatGPT 为代表的额大语言模型&#xff08;LLM&#xff09;增添更多功能。此外&#xff0c;LangChain 还具备 token 和上下文管理功能…

提高水泵可靠度与生产效率:故障诊断系统实践解析

水泵作为工厂生产线中不可或缺的设备之一&#xff0c;其正常运行对于生产效率和设备可靠性至关重要。然而&#xff0c;水泵故障可能会导致设备停机和生产中断&#xff0c;给企业带来巨大损失。 图.水泵&#xff08;iStock&#xff09; 为了解决这一问题&#xff0c;水泵健康管理…

机器人中的数值优化(四)—— 线搜索求步长(附程序实现)

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

CentOS7下更改、移动mysql数据存储的位置 附os 错误码13问题

1、看一下目前mysql数据存储的位 select datadir;从这个结果我们可以看出&#xff0c;当前mysql的datadir是在/var/lib/mysql 目录里的 2、关掉连接mysql的各种程序服务 systemctl stop mysqld 或 service mysql stop 3、在期望的位置创建目录&#xff08;datadir的新位置&a…

【走进Java框架】什么是Java框架,为什么要学习Java框架.

前言: 大家好,我是良辰丫,今天我们就要开始Java框架之旅了,我们在学习的征途中不断充实自己,提升自己的能力,加油哈,自我勉励一下,跟随我的步伐,一起前行哈.&#x1f48c;&#x1f48c;&#x1f48c; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#…

【前端面试知识点】- 大厂面试(四)

目录 1.什么是尾调用&#xff0c;使用尾调用有什么好处&#xff1f;2.ES6 模块与 CommonJS 模块有什么异同&#xff1f;3.for…in 和 for…of 的区别4.ajax、axios、fetch(前台请求后台数据的方式)5. parseInt()的用法6.浏览器运行机制7.border-radius:50%和100%究竟有什么区别…

Ansible从入门到精通【四】

大家好&#xff0c;我是早九晚十二&#xff0c;目前是做运维相关的工作。写博客是为了积累&#xff0c;希望大家一起进步&#xff01; 我的主页&#xff1a;早九晚十二 专栏名称&#xff1a;Ansible从入门到精通 立志成为ansible大佬 ★ansible-playbook应用 play-book的介绍pl…

lmsys.org最新的LLM排行榜

lmsys.org Large Model Systems Organization (LMSYS Org) 是一个开放的研究组织&#xff0c;由加州大学伯克利分校的学生和教师与加州大学圣地亚哥分校和卡内基梅隆大学合作创立。 他们的目标是通过共同开发开放数据集、模型、系统和评估工具&#xff0c;让每个人都能访问大…

App 应用测试方法以及测试思路

分析三种主流的移动 App 类型&#xff0c;并给出和普通web测试不同的地方&#xff0c;给出测试的思路&#xff0c;并给出部分场景组合。 移动端测试还是 PC 端测试&#xff0c;业务测试其实都属于 GUI 测试的范畴&#xff0c;所以基本的测试思路&#xff0c;比如基于页面对象封…

清凉一夏小风扇-Vue3版

这里写目录标题 前言 一、效果二、代码分享 前言 本片文章主要是做一个小练习&#xff0c;通过Vue来制作一个风扇练习css动画。 上一篇文章主要是讲解了React实现的部分 React实现部分看这里–> 一、效果 二、代码分享 1、主体框架 “sass”: “^1.62.1”, “vue”: “^3…