数据结构与算法--队列二

490 阅读8分钟

文章简要概述

  • 本文主要进行队列相关的算法题刷题题解记录,带你进一步了解队列相关算法以及如何解。
  • 这文一共有6道题,主要介绍leetcode中最近的请求次数面试题 17.09. 第 k 个数亲密字符串柠檬水找零煎饼排序任务调度器的解题思路。

与队列相关算法

最近的请求次数

最近的请求次数--leetcode

题目大意:

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例

输入: ["RecentCounter", "ping", "ping", "ping", "ping"]

[[], [1], [100], [3001], [3002]]

输出: [null, 1, 2, 3, 3]

解释: RecentCounter recentCounter = new RecentCounter();

recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1

recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2

recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3

recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

解题思路:

  • 是一个典型的队列问题,调用ping的时候就是检查现有队列中符合要求的值有几个。
  • 输入的t可以得到范围[t-3000,t],在这个范围区间符合的值,那对于小于t-3000的出队,留在队列里的就是符合的值,就长度即可。

代码:

function RecentCounter() {
   this.queue = []
};

/** 
 * @param {number} t
 * @return {number}
 */
RecentCounter.prototype.ping = function(t) {
   this.queue.push(t);
   while(this.queue[0] < t-3000) {
       this.queue.shift()
   }
   return this.queue.length;
};

面试题 17.09. 第 k 个数

面试题 17.09. 第 k 个数--leetcode

题目大意:

有些数的素因子只有 3,5,7,请设计一个算法找出第 k 个数。注意,不是必须有这些素因子,而是必须不包含其他的素因子。例如,前几个数按顺序应该是 1,3,5,7,9,15,21。

示例:

输入: k = 5

输出: 9

解题思路:

  • leetcode中很多题都是看了题目,都不知道在说啥,更别说怎么做了。
  • 看题中给出的数字 1、3、5、7、9、15、21,我们可以看出,1,31,51,71,33,53或35,7*3,这样一轮一轮下去。
  • 这道题我们使用p3、p5、p7的指针代表乘上对应3、5、7的轮数。每次从其中选出最小值,持续下去,直到找到k个符合的值。
  • k是当前队列中需要存放的数量

企业微信截图_204ee9c7-a330-4c8e-a907-e1d50284ace2.png

企业微信截图_4dbb4f52-fb22-463b-86b7-e7a8d7cf980c.png

代码:

 /**
 * @param {number} k
 * @return {number}
 */
var getKthMagicNumber = function(k) {
   const list = [1];
   let p3 = 0; 
   let p5 = 0;
   let p7 = 0;
   for(let i = 1; i <= k; i++) {
       const ans3 = list[p3] * 3;
       const ans5 = list[p5] * 5;
       const ans7 = list[p7] * 7;
       const ans = Math.min(ans3, ans5, ans7);
       list.push(ans);
       if (ans === ans3) p3++;
       if (ans === ans5) p5++;
       if (ans === ans7) p7++;
   }
   return list[k - 1];
};
>

亲密字符串

亲密字符串--leetcode

题目大意:

给你两个字符串 s 和 goal ,只要我们可以通过交换 s 中的两个字母得到与 goal 相等的结果,就返回 true ;否则返回 false 。

交换字母的定义是:取两个下标 i 和 j (下标从 0 开始)且满足 i != j ,接着交换 s[i] 和 s[j] 处的字符。

示例:

输入:s = "ab", goal = "ba"

输出:true

解释:你可以交换 s[0] = 'a' 和 s[1] = 'b' 生成 "ba",此时 s 和 goal 相等。


输入:s = "ab", goal = "ab"

输出:false

解释:你只能交换 s[0] = 'a' 和 s[1] = 'b' 生成 "ba",此时 s 和 goal 不相等。

解题思路:

  • 这道题需要把逻辑思路梳理清晰,常规判断就可以解答。
  • 1、两个字符串长度不同,不能满足条件。
  • 2、两个字符串的值完全相同,如果不存在重复的元素,不能满足条件,如都是abc,怎么交换都不能满足条件,都是aabc,存在重复的a,就可以交换满足条件。
  • 3、找到第一个不相等的元素位置i,
  • 4、找到第二不相等的元素位置j,如果j不存在,则不满足条件,否则判断两个字符串i,j交换位置的值是否相同。
  • 5、以上条件都满足,查找是否存在第三个不相同的值,存在则不满足条件。

代码:

// 判断是否存在重复元素
function isRepeat (str) {
   let arr = '';
   for(let k in str) {
       if (arr.indexOf(str[k]) === -1) {
            arr += str[k]
       } else {
           return true
       }
   }
   return false
}

function buddyStrings (s, goal) {
  if (s.length !== goal.length) return false;
  if (s === goal) return isRepeat(s);
  let i = 0;
  let j;
  // 找到第一个不同值位置
  while(s[i] === goal[i]) {
    i++;
  }
  j = i+1;
  // 是否存在第二不同值
  while(j < s.length && s[j] === goal[j]) {
      j++;
  }
  // 不存在第二不同值
  if (j === s.length) return false;
  // 第二不同值与第一个不同值是否相等
  if (s[i] !== goal[j] || s[j] !== goal[i]) return false;
  j++;
  // 之后的值是否还存在不同值
  while(j < s.length) {
      if (s[j] !== goal[j]) return false;
      j++;
  }
  return true;
};

柠檬水找零

柠檬水找零--leetcode

题目大意:

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。


示例:

案例一:

输入:bills = [5,5,5,10,20]

输出:true

解释:

前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。

第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。

第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。

由于所有客户都得到了正确的找零,所以我们输出 true。


案例二:

输入:bills = [5,5,10,10,20]

输出:false

解释:

前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。

对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。

对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。

由于不是每位顾客都得到了正确的找零,所以答案是 false。

解题思路:

  • 这道题需要把逻辑思路梳理清晰,常规判断就可以解答。
  • 对于5元的不需要找零,10元的只有一种找零,20元的找零有两种:一个10元、1个5元或3个5元,优先使用10元的方案

代码:

/**
 * @param {number[]} bills
 * @return {boolean}
 */
function lemonadeChange (bills) {
   let five = 0; let ten = 0;
   for(let bill of bills) {
       if (bill == 5) {
           five++;
       } else if (bill == 10) {
           if (five == 0) return false;
           five--;
           ten++;
       } else {
           if (ten > 0 && five > 0) {
               ten--;
               five--;
           } else if (five >= 3) {
               five -= 3;
           } else {
               return false;
           }
       }
   }
   return true;
};

煎饼排序

煎饼排序--leetcode

题目大意:

给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。

一次煎饼翻转的执行过程如下:

选择一个整数 k ,1 <= k <= arr.length
反转子数组 arr[0...k-1](下标从 0 开始)
例如,arr = [3,2,1,4] ,选择 k = 3 进行一次煎饼翻转,反转子数组 [3,2,1] ,得到 arr = [1,2,3,4] 。

以数组形式返回能使 arr 有序的煎饼翻转操作所对应的 k 值序列。任何将数组排序且翻转次数在 10 * arr.length 范围内的有效答案都将被判断为正确。

示例:

输入:[3,2,4,1]

输出:[4,2,4,3]

解释:

我们执行 4 次煎饼翻转,k 值分别为 4,2,4,和 3。

初始状态 arr = [3, 2, 4, 1]

第一次翻转后(k = 4):arr = [1, 4, 2, 3]

第二次翻转后(k = 2):arr = [4, 1, 2, 3]

第三次翻转后(k = 4):arr = [3, 2, 1, 4]

第四次翻转后(k = 3):arr = [1, 2, 3, 4],此时已完成排序。

解题思路:

  • 这道题思路不同的到的答案可能不同,所以答案不唯一。
  • 最后要的到一个有序的排列,首先找到数组中最大的值所在位置,将其反转到第一位,再将数组整个反转,这样最大值排序就是正确的了。
  • 使用相同方法找第二大元素,反转两次。重复以上操作,直到最后一个元素排序正确为止。

企业微信截图_6ebb26b9-130e-4f87-a12a-e6dbf69f843d.png

代码:

function reverse(arr, k) {
    for(let i = 0, j = k - 1; i < j; i++,j--) {
        const tem = arr[i];
        arr[i] = arr[j];
        arr[j] = tem;
    }
}
/**
 * @param {number[]} arr
 * @return {number[]}
 */
function pancakeSort (arr) {
   if (!arr.length) return []
   const res = [];
   const sortArr = [...arr].sort((a,b) => a-b);
   for(let i = arr.length - 1; i > 0; i--) {
       const index = arr.indexOf(sortArr[i]);
       if (index === i) continue;
       res.push(index + 1, i + 1);
       reverse(arr, index + 1);
       reverse(arr, i + 1);
   }
   return res;
};

任务调度器

任务调度器--leetcode

题目大意:

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间 。

示例:

案例一

输入:tasks = ["A","A","A","B","B","B"], n = 2

输出:8

解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B

在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。


案例二

输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2

输出:16

解释:一种可能的解决方案是: A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

解题思路:

  • 假设任务是[A,A,A,B,B,C,C],冷却时间2,我们的任务安排是A,B,C,A,B,C,A,冷却时间全部用满,刚好是任务的数量。
  • 假设任务是[A,A,A,B,B,B,C],冷却时间2,我们的任务安排是A,B,C,A,B,冷却,A,B,这种情况,假设任务最多的次数为max,次数为max的元素有m个,我们的最短时间的计算公式(max-1)*(n+1)+m;

企业微信截图_4e4678b2-bea6-4d22-a991-f63c79c60e7c.png

代码:

/**
 * @param {character[]} tasks
 * @param {number} n
 * @return {number}
 */
var leastInterval = function(tasks, n) {
   // 统计各个元素出现次数(借助lodash的方法)
   const oTaskCount = _.countBy(tasks);
   const values = Object.values(oTaskCount);
   // 得到元素出现最多的次数
   const max = Math.max(...values);
   // 统计满足最多次数的元素有几个
   const isMaxCount = values.filter(v => v === max).length;
   return Math.max(tasks.length, (max - 1) * (n + 1) + isMaxCount);
};
};

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是队列部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

数据结构与算法-队列一

数据结构与算法-链表一

数据结构与算法-链表二

数据结构与算法-链表三

如果文章对你有帮助,欢迎点赞,关注!