LeetCode0021

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4

输出:1->1->2->3->4->4

利用哑节点,简单的合并两链表:

/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1, l2) {
if (!l1) return l2;
if (!l2) return l1; let dummy = new ListNode();
let result = dummy; do {
if (l1.val < l2.val) {
dummy.next = l1;
l1 = l1.next;
} else {
dummy.next = l2;
l2 = l2.next;
}
dummy = dummy.next;
} while (l1 && l2); if (l1) dummy.next = l1;
if (l2) dummy.next = l2; return result.next;
};

但是结果:

执行用时 :80 ms, 在所有 JavaScript 提交中击败了46.30%的用户

内存消耗 :36 MB,在所有 JavaScript 提交中击败了10.12%的用户

看来大家都不利用哑节点提交嘛,我们试试递归。

/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1, l2) {
if (!l1) return l2;
if (!l2) return l1; if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
} else {
l2.next = mergeTwoLists(l1, l2.next);
} return l1.val < l2.val ? l1 : l2;
};

结果:

执行用时 :72 ms, 在所有 JavaScript 提交中击败了81.92%的用户

内存消耗 :36 MB,在所有 JavaScript 提交中击败了11.17%的用户

l1和l2的空间都是已有的,感觉空间复杂度已经无法再减小了。

LeetCode0023

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:[ 1->4->5, 1->3->4, 2->6 ]

输出: 1->1->2->3->4->4->5->6

简单的思考一下可以先排前俩个,然后排序好后的数组再跟下一个排,也就是调用k-1次mergeTwoLists,假如每个数组长度固定为n的话,比较第一个和第二个的时间是O(2n),合并好后成为2n长度的链表,再跟第3个数组merge的时候,时间复杂度则为O(2n+n),也就是O(3n),比较到第K个数组的时候就要O(k*n)的时间。那么每次至少比较2n+3n+4n+...+k*n,当k=n的时候至少是O(n3/2)的时间复杂度。

/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function (lists) {
if (lists === undefined || lists.length === 0) return null;
let result = lists[0];
for (let i = 1, lens = lists.length; i < lens; i++) {
result = mergeTwoLists(result, lists[i]);
}
return result;
};

结果:

执行用时 :792 ms, 在所有 JavaScript 提交中击败了7.48%的用户

内存消耗 :38.6 MB,在所有 JavaScript 提交中击败了63.16%的用户

这个执行时间不是很理想,我们接着尝试。

思路:

  • 假设一开始就让第一个链表成为结果链表;
  • 对于其后的每个链表,遍历每个节点,将节点插入到结果链表里;
  • 如果节点一直比结果链表第一个小,则将节点插入到头节点;
  • 假如节点比结果链表最后一个节点还大,则直接将整个链表插入到结果链表后面即可;
  • 如果找到合适的插入位置了,则将节点插入,并且因为是有序链表,后续的节点值只要从这里开始往后进行比较即可。
  • 遇到新链表了则初始化比较位置。
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function (lists) {
if (lists === undefined || lists.length === 0) return null;
let result = lists[0];
if (lists.length > 1) {
for (let i = 1, lens = lists.length; i < lens; i++) {
let row = lists[i];//有序列表
if (row) {
let pos = result;//记录当前队列比较到哪个位置了,初始值为最开始的节点
let posParent = null;
do {
//找到一个pos使得pos.val>=row.val,此时将row插入到pos前面
while (pos && row.val > pos.val) {
posParent = pos;
pos = pos.next;
} if (posParent === null) {
//一直比第一个小
pos = row;
row = row.next;
pos.next = result;
result = pos;
}
else {
if (pos === null) {
//当前这个节点比已经存在的链表内所有节点都大,直接将当前队列追加上即可比较下一个链表了
posParent.next = row;
pos = row;
break;
}
else {
//找到某个节点使得它比当前节点大
posParent.next = row;
row = row.next;
posParent = posParent.next;
posParent.next = pos;
}
} } while (row);
}
}
}
return result;
};

结果:

执行用时 :300 ms, 在所有 JavaScript 提交中击败了29.06%的用户

内存消耗 :37.3 MB,在所有 JavaScript 提交中击败了97.74%的用户

看这样子我们的执行时间还是很长,但我们几乎没有使用额外的内存,考虑到对上面这个算法的改进,时间应该主要消耗在每次都将比较位置初始化为第一个上了,这样挨个比较节点并且还要把他们进行准确链接很慢。我们现在的内存基本上不是我们的设计瓶颈。

我们新增一个数组,就专门用来存每个单独的节点,然后我们用快速排序将节点按值有序排好,然后再将节点链接起来即可。

  • 遍历所有链表得到所有节点;
  • 快排;
  • 组合节点输出。
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function (lists) {
if (!lists || lists.length === 0) return null;
let result = [];
let lens = lists.length;
let head;
for (let i = 0; i < lens; i++) {
head = lists[i];
while (head && head.val !== null) {
result.push(head);
head = head.next;
}
} let lens2 = result.length;
QuickSort(result, 0, lens2 - 1);
let dummyNode = new ListNode();
head = dummyNode;
while (lens2 > 0) {
dummyNode.next = result.shift();
dummyNode = dummyNode.next;
dummyNode.next = null;
lens2--;
} return head.next;
}; function QuickSort(arr, i, j) {
if (i < j) {
let m = partition(arr, i, j);
QuickSort(arr, i, m - 1);
QuickSort(arr, m + 1, j);
}
} function partition(arr, i, j) {
let pivot = arr[i].val;
let m = i;
for (let k = i + 1; k <= j; k++) {
if (arr[k].val < pivot) {
m++;
swap(arr, k, m);
}
}
swap(arr, i, m);
return m;
} function swap(arr, i, j) {
let tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}

结果:

执行用时 :104 ms, 在所有 JavaScript 提交中击败了78.27%的用户

内存消耗 :38.6 MB,在所有 JavaScript 提交中击败了64.66%的用户

其实还可以进一步优化,快排最重要的是pivot的取值,我们知道我们合并的链表都是有序数组,那么我们的基准值可以不找第一个,而是取中间值,以此我们修正一下partition函数。

function partition(arr, i, j) {
if (i < j) {
let mid = Math.floor((i + j) / 2);
swap(arr, mid, i);
let pivot = arr[i].val;
let m = i;
for (let k = i + 1; k <= j; k++) {
if (arr[k].val < pivot) {
m++;
swap(arr, k, m);
}
}
swap(arr, i, m);
return m;
}
//i===j或者i>j都不需要做任何处理
return -1;
}

结果快了4ms。

LeetCode Day 11的更多相关文章

  1. leetcode笔记11 First Unique Character in a String

    题目描述: Given a string, find the first non-repeating character in it and return it's index. If it does ...

  2. LeetCode:11. ContainerWithWater(Medium)

    原题链接:https://leetcode.com/problems/container-with-most-water/description/ 题目要求:给定n个非负整数a1,a2,...,an  ...

  3. 2017-3-13 leetcode 4 11 15

    ji那天居然早起了,惊呆我了,眼睛有点儿疼,一直流泪....继续保持 ========================================================== leetco ...

  4. 【LeetCode】11. 盛最多水的容器

    题目 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...

  5. 【LeetCode】11. Container With Most Water 盛最多水的容器

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:盛水,容器,题解,leetcode, 力扣,python ...

  6. 【LeetCode】11. Container With Most Water

    题目: Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, a ...

  7. leetcode problem 11 Container With Most Water

    Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). ...

  8. leetcode第11题--Container With Most Water

    Problem: Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate ...

  9. LeetCode OJ 11. Container With Most Water

    Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai).  ...

随机推荐

  1. MySQL--SHOW ENGINE INNODB STATUS

    ===================================== -- :: 0x7f305b965700 INNODB MONITOR OUTPUT =================== ...

  2. shell中获取文件目录方法

    1.``:表示执行对应的命令,嵌套时使用`\`\``,注意\进行转义,同时执行多个命令时使用:隔开file=`cd "\`dirname $0\`";pwd`echo $file ...

  3. node,npm,webpack,vue-cli模块化编程安装流程

    首先什么都不要管,先装环境. pip是万能的!!! 安装node: pip3 install node 安装npm:   pip3 install npm 安装webpack: npm install ...

  4. echarts图表重设尺寸

    在绘制chart的方法中添加下面语句,则会在尺寸变化的时候,重新绘制图表 window.addEventListener("resize", function () { myCha ...

  5. goweb-会话控制

    会话控制 HTTP 是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不 能区分中两次请求是否由一个客户端发出.这样的设计严重阻碍的 Web 程序的设计. 如:在我们进行网购时,买了一条裤子 ...

  6. 生成随机数(Random类)和获取用户输入(Scanner类)

    生成指定范围内的随机数 Math.random() 生成随机数,随机数在0到1之间,类型是 double. public class randCase { public static void mai ...

  7. JAVA初学者——算数运算符

    Hello!大家好,我是浩宇大熊猫,又是学习java的一天,开开森森~ 运算符:也就是对常量或者变量进行操作的符号 表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式,不同的 ...

  8. PAT Advanced A1104 Sum of Number Segments (20) [数学问题]

    题目 Given a sequence of positive numbers, a segment is defined to be a consecutive subsequence. For e ...

  9. Java基础三(2020.1.15)

    学习内容: 1.Java流程控制之循环结构 2.Java数组 3.Java方法 1.随机数:math.random()得到0-1之间的数     math.random()*10+1得到1-10之间的 ...

  10. Maven--可选依赖

    假设有这样换一个依赖关系,项目 A 依赖于项目 B,项目 B 依赖于项目 X 和 Y,B 对于 X 和 Y的依赖都是可选依赖: A -> B B -> X(可选) B -> Y(可选 ...