LeetCode Day 11
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入: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的空间都是已有的,感觉空间复杂度已经无法再减小了。
合并 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的更多相关文章
- 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 ...
- LeetCode:11. ContainerWithWater(Medium)
原题链接:https://leetcode.com/problems/container-with-most-water/description/ 题目要求:给定n个非负整数a1,a2,...,an ...
- 2017-3-13 leetcode 4 11 15
ji那天居然早起了,惊呆我了,眼睛有点儿疼,一直流泪....继续保持 ========================================================== leetco ...
- 【LeetCode】11. 盛最多水的容器
题目 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...
- 【LeetCode】11. Container With Most Water 盛最多水的容器
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:盛水,容器,题解,leetcode, 力扣,python ...
- 【LeetCode】11. Container With Most Water
题目: Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, a ...
- leetcode problem 11 Container With Most Water
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). ...
- leetcode第11题--Container With Most Water
Problem: Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate ...
- LeetCode OJ 11. Container With Most Water
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). ...
随机推荐
- 如何实现MVC ActionResult 返回类型为JavaScriptResult
必需的js引用文件 <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>@Scripts ...
- 吴裕雄--天生自然ShellX学习笔记:Shell 数组
数组中可以存放多个值.Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似). 与大部分编程语言类似,数组元素的下标由0开始. Shell 数组用括号来 ...
- DAO层使用mybatis框架有关实体类的有趣细节
1.根据个人习惯,将储存那些数据库查询结果集有映射关系的实体类的Package包名有如下格式: cn.bjut.domain cn.bjut.pojo cn.bjut.model cn.bjut.en ...
- dfs+剪枝 poj1011
Sticks Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 113547 Accepted: 26078 问题描述 Ge ...
- 第二季 第十一天 part2
const greeting = function() { // 注意,这个 this.name 取决于谁调用了 greeting() 函数 console.log('Hi, ', this.name ...
- Python—使用列表构造栈数据结构
class Stack(object): """ 使用列表实现栈 """ def __init__(self): self.stack = ...
- ZJNU 1534 - Problem Robot--高级
因为是从(0,0)点开始以1,3,9,27,....的步数走的 其实可以每走一步后,以机器人为中心,平面所有坐标全部缩小3倍 那么本应该走3步的路现在只需要走1步就可以到达那个点 那么对于机器人来说这 ...
- Halcon中将16位的图像转化为8位的图像
Halcon中Image有多种像素表示方式,这方面网上找到的资料比较少,有一张大恒图像培训的文档中提到过,感觉描述比较准确: 里面有四种类型比较类似:uint2.int1.int2.int4. 区分起 ...
- Lua与C++交互初探之C++调用Lua
Lua与C++交互初探之C++调用Lua 自从学习了lua这个脚本语言之后,无时不想着将他与c/c++联系起来,看看他真正的威力.奈何水平有限,网上找的代码无论怎样都无法运行成功.我知道是我少了某一步 ...
- 实现文件上下文管理(\_\_enter\_\_和\_\_exit\_\_)
实现文件上下文管理(__enter__和__exit__) 我们知道在操作文件对象的时候可以这么写 with open('a.txt') as f: '代码块' 上述叫做上下文管理协议,即with语句 ...