最近遇到的两个有意思的问题(合并K个有序数组,打印省区市所有与目标市名字相同的路径)

最近遇到的几个有意思问题,记录分享一下。

1. 合并三个有序数组(合并K个有序数组)

假如有这么三个数组:(arr包含的三个数组)

let arr = [
  [1, 3, 5, 7],
  [2, 4, 6],
  [0, 8, 9, 10, 11],
];

问题:K个数组,总共N个元素,合并成一个有序数组。(以升序为例)

大家一眼看上去肯定会想到归并排序,合并两个有序数组是归并排序的最后一步的动作,通过两个指针引导比较大小依次合并两个有序序列。

问题是三个数组的话,用三指针去做辅助貌似思路上看似直白,实际写代码控制边界的时候要做N多个if else判断,代码上很啰嗦。如果上升到合并四个数组,五个数组话,难道要要四指针,五指针,一个个合并的时候判断每个数组是否到达边界?

如果只是三个数组的话,我们可以先合并两个数组,再合并剩下的,时间复杂度也是O(N)级别的。

但如果回归到这个问题的本质通用模型上,有K个有序数组呢?

一个直接便捷的办法是concat所有数组,快速排序,平均时间复杂度有N Log(N)。这种方法最直接,但是肯定不应该是最优解,子数组在有序的情况下,应该有不需要比较的情况。

我的思路是利用堆构造优先队列预处理下各个子数组,用一个cur记录下每个数组的当前指针。初始化的时候,建立一个K大小的优先队列,初始化元素为每个数组的第一个元素和这个元素所在的数组序号,用seq表示,eg:K个这样的node:{seq:0,value:xx}。每次取出top元素,根据此元素的seq,将子数组的cur指针后移。总共取N次,优先队列二叉堆的每次调整时间复杂度为LogK, 所以时间复杂度为N log(K)。

K一般远小于N的,所以此时间复杂度比concat+排序要好的。

代码:

function PriorityQueue() {
  // 方便计算,将第一位置空
  this.list = [{}];
}

PriorityQueue.prototype.size = function () {
  return this.list.length - 1;
};

PriorityQueue.prototype.empty = function () {
  return this.list.length === 1;
};

PriorityQueue.prototype.push = function (data) {
  this.list.push(data);
  this._moveUp();
};

// 向上调整数
PriorityQueue.prototype._moveUp = function () {
  let newPos = this.list.length - 1;
  let parent = Math.floor(newPos / 2);
  let isChange = true;
  while (isChange) {
    isChange = false;
    //父子结点比较
    // 注意这个问题构造的是对象元素,值大小在对象的value key上
    if (this.list[parent].value > this.list[newPos].value) {
      let temp = this.list[parent];
      this.list[parent] = this.list[newPos];
      this.list[newPos] = temp;
      isChange = true;
      newPos = parent;
      parent = Math.floor(newPos / 2);
    }
  }
};

// 向下调整
PriorityQueue.prototype._moveDown = function () {
  let newPos = 1;
  let isChange = true;
  while (isChange) {
    isChange = false;
    //父子结点比较
    let leftSonPos = newPos * 2;
    let rightSonPos = newPos * 2 + 1;
    let leftSonVal = this.list[leftSonPos];
    let rightSonVal = this.list[rightSonPos];

    if (typeof leftSonVal === "undefined" && typeof rightSonVal) break;

    let pos;
    // 要注意有结点不存在的情况
    if (
      typeof leftSonVal !== "undefined" &&
      typeof rightSonVal === "undefined"
    ) {
      pos = leftSonVal.value < this.list[newPos].value ? leftSonPos : newPos;
    } else if (
      typeof leftSonVal === "undefined" &&
      typeof rightSonVal !== "undefined"
    ) {
      pos = rightSonVal.value < this.list[newPos].value ? rightSonPos : newPos;
    } else {
      pos = leftSonVal.value < rightSonVal.value ? leftSonPos : rightSonPos;
      pos = this.list[newPos].value < this.list[pos].value ? newPos : pos;
    }

    if (pos === newPos) break;
    let temp = this.list[pos];
    this.list[pos] = this.list[newPos];
    this.list[newPos] = temp;
    isChange = true;
    newPos = pos;
  }
};

PriorityQueue.prototype.pop = function () {
  let res = this.top();
  this.list[1] = this.list[this.list.length - 1];
  this.list.splice(this.list.length - 1, 1);
  this._moveDown();
  return res;
};

PriorityQueue.prototype.top = function () {
  return this.list[1];
};

PriorityQueue.prototype.back = function () {
  return this.list[this.list.length - 1];
};

let arr = [
  [1, 3, 5, 7],
  [2, 4, 6],
  [0, 8, 9, 10, 11],
];
let arrObj = [
  { cur: 0, arr: [1, 3, 5, 7] },
  { cur: 0, arr: [2, 4, 6] },
  { cur: 0, arr: [0, 8, 9, 10, 11] },
];
// 如果使用三个指针比较麻烦,判断大小还要判断某个到达边界情况。如果只是合并三个有序数组的话我们可以双指针法先合并一个,然后再合并剩下的一个
// 问题可以直接引申到合并K个数组
// 思路即是建立一个K大小的最小堆,最小堆的元素初始化为每个数组的最开始元素。取最小值后将这个元素所在的数组再放进堆里即可。
function mergeArr(arrObj) {
  let ans = [];
  let queue = new PriorityQueue();

  // 求n
  let n = 0;
  for (let i = 0; i < arrObj.length; i++) {
    n += arrObj[i].arr.length;
  }

  // init 队列,,初始化指针map
  for (let i = 0; i < arrObj.length; i++) {
    queue.push({ seq: i, value: arrObj[i].arr[0] });
    arrObj[i].cur++;
  }

  while (n--) {
    let top = queue.pop();
    // console.log(top);
    let arrIndex = top.seq;
    let cur = arrObj[arrIndex].cur;
    let curArr = arrObj[arrIndex].arr;
    ans.push(top);
    if (cur < curArr.length) {
      queue.push({ seq: arrIndex, value: curArr[cur] });
      arrObj[arrIndex].cur++;
    }
  }
  console.log(ans);
  return ans.map((v) => v.value);
}
mergeArr(arrObj);

因为不一定是完全二叉树,所以建堆的过程需要判断是否有子节点。还有一点就是要预处理各个有序子数组,利用seq,cur等帮助合并。(关于堆,树的算法基础强烈推荐啊哈算法这本书,大学时期我都把这本书翻烂了=-=,学生的时候还是很感谢这本书的。。)

2. 打印目标节点值和叶子节点相同的所有路径

前端常见的省区市这种三级数据结构,如果给出一个区的名字,找出所有和区名字相同的所有三级路径结构。

eg:

let tree = {
  name: "china",
  children: [
    {
      name: "江苏省",
      children: [
        { name: "G市", children: [] },
        { name: "M市", children: [] },
      ],
    },
    {
      name: "河南省",
      children: [
        { name: "M市", children: [] },
        { name: "D市", children: [] },
      ],
    },
    {
      name: "北京市",
      children: [
        { name: "G区", children: [] },
        { name: "A区", children: [{ name: "M市", children: [] }] },
      ],
    },
  ],
};

此问题回归到通用模型上为多叉树上打印目标节点值和叶子节点相同的所有路径,我的思路用一个path数组维护路径,每到一层push当前层节点,到叶子节点判断此叶子节点值是否和目标值相同,相同则把当前path数组push进ans结果数组,每层递归结束离开此层时记得pop掉path的此层节点值。

代码:

/**
  假设有这么一个tree的数据结构,给定一个叶子节点的名称,打印出名称和目标节点相同的的所有路径
  [[china,M市],[china,henan,M市],[china,beijing,A区,M市]]
 */

let tree = {
  name: "china",
  children: [
    {
      name: "jiangsu",
      children: [
        { name: "G市", children: [] },
        { name: "M市", children: [] },
      ],
    },
    {
      name: "henan",
      children: [
        { name: "M市", children: [] },
        { name: "D市", children: [] },
      ],
    },
    {
      name: "beijing",
      children: [
        { name: "G区", children: [] },
        { name: "A区", children: [{ name: "M市", children: [] }] },
      ],
    },
  ],
};

let ans = [];
function printPathArr(node, target, path = []) {
  let children = node.children;
  path.push(node.name);
  if (children.length) {
    for (let i = 0; i < children.length; i++) {
      let curNode = children[i];
      printPathArr(curNode, target, path);
    }
  }

  if (!children.length && node.name === target) {
    // 只有满足这个条件才push到arr
    ans.push(path.slice(0));
  }
  // 这里每次pop,那么上面的push也就要每次都要push,这样路径才不乱
  // 这一层结束,返回上一层
  path.pop();
}
printPathArr(tree, "M市");
console.log(ans);

与此相同的问题模型还有二叉(多叉)树中找到目标节点的所有祖先节点,和为某一值的所有路径等等。相似问题都可用我的上面方法灵活解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: MySQL省区三级联动是指在数据库中,建立三个表格来分别存储、省和区的信息,并通过外键实现它们之间的关联。这种设计可以用于它们之间的数据查询和管理。 在这种设计中,首先创建一个“省份”表格,包含“省份ID”和“省份名称”两个字段。然后创建一个“城”表格,包含“城ID”、“城名称”和“省份ID”三个字段,其中“省份ID”字段对应“省份”表格中的“省份ID”字段,用于关联两个表格。最后创建一个“区县”表格,包含“区县ID”、“区县名称”和“城ID”三个字段,其中“城ID”字段对应“城”表格中的“城ID”字段,用于关联两个表格。 当需要查询某个的所有省份时,可以直接通过“城”表格中的“省份ID”字段来连接“省份”表格,通过使用JOIN语句来实现查询。同理,当需要查询某个区的所属和省份时,可以通过“区县”表格中的“城ID”字段来连接“城”表格和“省份”表格。 此外,在进行数据插入和更新时,可以通过在相关表格中插入或更新相应的数据来实现三级联动的更新。 通过建立这种省区三级联动的数据库设计和设置,可以方便地进行、省和区的信息管理和查询。比如,在用户注册填写地址时,可以通过选择、省和区的下拉菜单,将用户选择的三级联动数据保存到数据库中,从而提供方便而准确的地区信息。 ### 回答2: MySQL是一种关系型数据库管理系统,可以使用其提供的功能轻松实现省区三级联动。 首先,我们需要设计数据库表结构。可以创建三张表来分别表示、省和区。每个表都包含对应的ID和名称字段,其中表还应包含省ID字段,省表还应包含区ID字段。通过使用外键约束,我们可以确保数据的一致性和完整性。 接下来,我们需要通过MySQL的语句来实现联动功能。首先,我们可以通过查询获取省表中的所有记录,然后在网页中生成省份的下拉列表。当用户选择某个省份时,我们可以通过相应的省ID查询表中该省份对应的所有级记录,并在网页中动态生成城的下拉列表。最后,当用户选择某个城时,我们可以根据ID查询区表中该城对应的所有区级记录,并在网页中生成区的下拉列表。 为了实现联动功能,我们还可以借助JavaScript来监听用户的选择动作,当用户选择省份时,通过Ajax请求获取该省份对应的城数据,然后根据返回的数据动态生成城下拉列表。同样地,当用户选择城时,再次通过Ajax请求获取该城对应的区数据并生成区下拉列表。 总结来说,要实现MySQL的省区三级联动,我们需要考虑数据库表结构设计、MySQL查询语句和前端JavaScript的配合使用。通过这些方法,我们可以轻松实现、省、区的三级联动功能。 ### 回答3: MySQL省区三级联动指的是根据用户在页面上选择的省、、区/县信息,向MySQL数据库中查询并返回对应的结果。 首先,需要在MySQL数据库中创建相关的表格来存储省、、区/县的信息。可以创建三张表,分别存储省、、区/县的名称、ID、所属关系等信息。 在页面上的省、、区/县选择框中,首先加载省的信息,当用户选择了某个省时,通过前端页面的Ajax异步请求,将选择的省ID发送给后端服务器。 后端服务器端接受到省ID后,根据该ID在MySQL数据库中查询对应的城信息,并返回给前端页面。 前端页面接收到城信息后,将城信息加载到选择框中。当用户选择了某个时,将选择的ID发送给后端服务器。 后端服务器端接受到ID后,根据该ID在MySQL数据库中查询对应的区/县信息,并返回给前端页面。 前端页面接收到区/县信息后,将区/县信息加载到区/县选择框中,完成三级联动的过程。 通过MySQL省区三级联动,可以方便用户快速选择所需要的具体位置信息,提高用户体验。同时,在后端服务器端和MySQL数据库中的查询操作,需要进行相应的优化,以提高系统的响应速度,保证联动操作的流畅性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值