1.翻转链表
当输入链表{1,2,3},对应的翻转链表为{3,2,1}
方法一:用数组顺序存储链表,返回从数组末尾将指针翻转,这种做法时间和空间复杂度都为O(n)
function ReverseList(pHead)
{
// write code here
if(!pHead){
return null;
}
let arr = [];
while(pHead){
arr.push(pHead);
pHead = pHead.next;
}
for(let i=arr.length-1;i>0;i--){
arr[i].next=arr[i-1];
}
arr[0].next=null;
return arr[arr.length-1];
}
方法2:用两个指针,遍历链表时,pre总是cur的前一个指针,并且将cur移到下一个位置时,将当前cur用tmp指针保存,并让tmp的next指向pre,完成翻转
function ReverseList(pHead)
{
// write code here
let pre=null;
let tmp=null;
let cur=pHead;
while(cur){
tmp=cur;
cur = cur.next;
tmp.next=pre;
pre=tmp;
}
return pre;
}
2.链表内指定区间反转
题目: 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
示例:
输入:{1,2,3,4,5},2,4
返回值:{1,4,3,2,5}
方法一:上面的例子,我通过第一题的方法翻转2到4区间的链表这时候就变成了2<-3<-4,当需要开始翻转时,用res_pre保存节点1,res_start保存节点2,当计数到n也就是4时,将res_pre.next指向4,res_start指向5,这时候head就变成1->4->3->2->5
function reverseBetween( head , m , n ) {
// write code here
let pre=null;
let tmp=null;
let res_start=null;
let res_pre=null;
let cnt=0;
let cur=head;
while(cur){
cnt++;
tmp=cur;
cur=cur.next;
if(cnt>m && cnt<=n){//翻转此区间链表
tmp.next=pre;
}
if(cnt===m){
res_pre = pre;
res_start = tmp;
}
if(cnt===n){
if(!res_pre){//区间从1开始的情况
head=tmp;
res_start.next=cur;
}else{
res_pre.next=tmp;
res_start.next=cur;
}
}
pre=tmp;
}
return head;
}
方法二:leetcode上大佬的解法,代码清晰不少
var reverseBetween = function(head, left, right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
const dummyNode = new ListNode(-1);
dummyNode.next = head;
let pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (let i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
let rightNode = pre;
for (let i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
let leftNode = pre.next;
let curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
};
const reverseLinkedList = (head) => {
let pre = null;
let cur = head;
while (cur) {
const next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
3. 链表中的节点每k个一组翻转
题目: 将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表。如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样。
要求空间复杂度 O(1),时间复杂度 O(n)
输入:{1,2,3,4,5},2
返回值:{2,1,4,3,5}
题解: 有了第二题做铺垫,这道题做起来也比较得心应手,一直循环截取k个子链表并将其翻转,然后链接到原链表中
function reverseKGroup( head , k ) {
// write code here
let cnt = 0;
let cur = new ListNode(-1);
cur.next = head;
let pre = cur;
while(cur){
for(let i=0;i<k && cur;i++){
cur=cur.next;
}
if(!cur){
break;
}
cnt++;
//切断一个子链表
let leftNode = pre.next;
let rightNode = cur;
let tmp=cur.next;
//切断链接
pre.next = null;
rightNode.next=null;
reverseLink(leftNode)
if(cnt===1){//找到第一个k结尾的节点这是头结点
head=rightNode;
}
//连上翻转子链表
pre.next = rightNode
leftNode.next = tmp;
//继续遍历
pre = leftNode;
cur = leftNode;
}
return head;
}
function reverseLink(node){
let pre=null;
let cur=node;
let next=null;
while(cur){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
4.合并两个排序的链表
题目: 输入两个递增的链表,合并这两个链表并使得新链表中的节点仍然是递增排序的
示例:
输入:{1,3,5},{2,4,6}
返回值:{1,2,3,4,5,6}
代码:
var mergeTwoLists = function(l1, l2) {
let node = new ListNode(-1);
let head=node;
while(l1 && l2){
if(l1.val>=l2.val){
node.next=l2;
l2 = l2.next;
}else{
node.next=l1;
l1=l1.next;
}
node=node.next;
}
if(l1){
node.next=l1;
}
if(l2){
node.next=l2;
}
return head.next;
};
5.合并k个已排序的链表
- 方法1:递归分治
function mergeKLists( lists ) {
// write code here
return merge(lists, 0, lists.length-1)
}
function merge(lists, l, r){
if(l===r){
return lists[l];
}
if(l>r){
return null;
}
let mid = parseInt((l+r)/2);
return mergeTwoLists(merge(lists,l, mid), merge(lists, mid+1, r));
}
function mergeTwoLists(node1, node2){
let node = new ListNode(-1);
let head = node;
while(node1 && node2){
if(node1.val>=node2.val){
node.next = node2;
node2 = node2.next;
}else{
node.next = node1;
node1 = node1.next;
}
node = node.next;
}
if(node1){
node.next = node1;
}
if(node2){
node.next = node2;
}
return head.next;
}
方法2:归并排序
function mergeKLists( lists ) {
// write code here
if (lists.length === 0) return null;
const L = new ListNode(-1);
while (lists.length > 1) {
for (let i = 0, j = lists.length - 1; i < j; i++, j--) {
lists[i] = mergeTwo(lists[i],lists[j])
lists.pop()
}
}
return lists[0];
}
function mergeTwo(node1, node2){
let node = new ListNode(-1);
let head = node;
while(node1 && node2){
if(node1.val>=node2.val){
node.next = node2;
node2 = node2.next;
}else{
node.next = node1;
node1 = node1.next;
}
node = node.next;
}
node.next = node1?node1:node2;
return head.next;
}
6.判断链表中是否有环
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
/*
* function ListNode(x){
* this.val = x;
* this.next = null;
* }
*/
/**
*
* @param head ListNode类
* @return bool布尔型
*/
function hasCycle( head ) {
// write code here
if(!head){
return null;
}
let fast=head;
let slow=head;
while(fast && fast.next){
fast = fast.next.next;
slow = slow.next;
if(fast==slow){
return true;
}
}
return false;
}
8.链表中倒数最后k个节点
题目: 输入长度为n的链表,返回该链表中倒数第k个节点
输入:{1,2,3,4,5},2
返回值:{4,5}
方法1:遍历链表时用数组存储节点,返回数组长度-k+1个数就是倒数第k个节点
function FindKthToTail( pHead , k ) {
// write code here
if(!pHead) return null;
var nodes = [];
while(pHead.next){
nodes.push(pHead);
pHead = pHead.next;
}
return nodes[nodes.length-k+1]
}
方法2:用两个指针,fast指针比slow指针多走k步,当fast指针走到链表末尾时,slow指针刚好指向倒数第k个节点
function FindKthToTail( pHead , k ) {
// write code here
let fast = pHead;
let slow = pHead;
let i=0;
for(;i<k && fast;i++){
fast = fast.next;
}
if(!fast && i<k){//k的长度大于链表节点个数
return null;
}
while(fast){
fast=fast.next;
slow=slow.next;
}
return slow;
}
9.删除链表的倒数第n个节点
题目: 给定一个链表,删除链表的倒数第n个节点并返回链表的头指针
示例:
输入:{1,2},2
返回值:{2}
输入:{1,2,3,4,5},2
返回值:{1,2,3,5}
方法1: 和上一题一样,首先slow指向倒数第n个节点,然后直接让pre指向slow的下一个节点,跳过倒数第n个节点即可
function removeNthFromEnd( head , n ) {
// write code here
let fast=head;
let slow=head;
let pre=new ListNode(-1);
let node=pre;
node.next = head;
let i=0;
for(;i<n && fast;i++){
fast=fast.next;
}
if(!fast && i<n){
return head;
}
while(fast){
pre=slow;
fast=fast.next;
slow=slow.next;
}
pre.next = slow.next;
return node.next;
}
10.两个链表的第一个公共节点
题目: 输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。
示例:
输入:{1,2,3},{4,5},{6,7}
返回值:{6,7}
说明:第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的
题解: 两个同频的指针l1
和l2
,如果存在公共节点,则如l1
的经过路程是m1->m3->m2
,而l2
的经过路程是m2->m3->m1
,这两个指针一定会在公共节点上相遇
function FindFirstCommonNode(pHead1, pHead2)
{
// write code here
var l1=pHead1;
var l2=pHead2;
while(l1!=l2){
l1=(l1==null)?pHead2:l1.next;
l2=(l2==null)?pHead1:l2.next;
}
return l1;
}
11.链表相加
题目: 假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
示例:
输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
方法:用数组分别存储head1和head2,再反向相加,如果数组不齐则添0补齐
function addInList( head1 , head2 ) {
// write code here
let s1=[];
let s2=[];
let carry=0;
let ans = null;
while(head1){
s1.push(head1.val);
head1=head1.next;
}
while(head2){
s2.push(head2.val);
head2=head2.next;
}
while(s1.length!==0 || s2.length!==0 || carry!==0){
let a = (s1.length===0) ? 0 : s1.pop();
let b = (s2.length===0) ? 0 : s2.pop();
let cur = a + b + carry;
carry = parseInt(cur / 10);
cur %= 10;
let node = new ListNode(cur);
node.next = ans;
ans = node;
}
return ans;
}
12. 单链表的排序
给定一个节点数为n的无序单链表对其按升序排序
示例:
输入:{1,3,2,4,5}
返回值:{1,2,3,4,5}
方法1:
对链表自顶向下归并排序的过程如下。
- 找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 22 步,慢指针每次移动 11 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
- 对两个子链表分别排序。
- 将两个排序后的子链表合并,得到完整的排序后的链表。
上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 11,即当链表为空或者链表只包含 1 1 个节点时,不需要对链表进行拆分和排序。
function sortInList( head ) {
// write code here
return toSortList(head, null);
}
const toSortList = (head, tail) => {
if (head === null) {
return head;
}
if (head.next === tail) {
head.next = null;
return head;
}
let slow = head, fast = head;
while (fast !== tail) {
slow = slow.next;
fast = fast.next;
if (fast !== tail) {
fast = fast.next;
}
}
const mid = slow;
return merge(toSortList(head, mid), toSortList(mid, tail));
}
const merge = (head1, head2) => {
const dummyHead = new ListNode(0);
let temp = dummyHead, temp1 = head1, temp2 = head2;
while (temp1 !== null && temp2 !== null) {
if (temp1.val <= temp2.val) {
temp.next = temp1;
temp1 = temp1.next;
} else {
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 !== null) {
temp.next = temp1;
} else if (temp2 !== null) {
temp.next = temp2;
}
return dummyHead.next;
}
13.判断一个链表是否为回文结构
/*
* function ListNode(x){
* this.val = x;
* this.next = null;
* }
*/
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
function isPail( head ) {
// write code here
let arr = [];
while(head){
arr.push(head.val);
head = head.next;
}
let i=0;
let j=arr.length-1;
for(;i<arr.length, j>=0; i++, j--){
if(arr[i]!==arr[j]){
return false;
}
}
return true;
}
14.链表的奇偶重排
给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。注意是节点的编号而非节点的数值。
示例:
输入:{1,2,3,4,5,6}
返回值:{1,3,5,2,4,6}
说明:1->2->3->4->5->6->NULL
重排后为
1->3->5->2->4->6->NULL
方法1:遍历链表时,根据计数count将节点存储到对应的奇偶数组中,然后再将奇偶数组进行链接
function oddEvenList( head ) {
// write code here
let odd = [];
let even = [];
let count = 0;
let res = new ListNode(-1);
while(head){
count++;
let node = new ListNode(head.val);
if(count%2===1){
odd.push(node);
}else{
even.push(node);
}
head = head.next;
}
for(let i=0;i<odd.length-1;i++){
odd[i].next=odd[i+1];
}
if(even.length===0){
return odd[0];
}
odd[odd.length-1].next=even[0];
for(let j=1;j<even.length;j++){
even[j-1].next=even[j];
}
even[even.length-1].next=null;
return odd[0];
}
方法2:leetcode大佬的解法果然要简介许多
var oddEvenList = function(head) {
if (head == null) return null;
let odd = head//奇数节点链表尾指针
let cur = head.next//偶数节点链表尾指针&&当前遍历节点
let even =cur//偶数节点链表(head将拆分为奇数节点链表)
while (cur != null && cur.next != null) {
//奇节点指针移动
odd.next = cur.next;
odd = odd.next;
//偶节点指针移动以及更新当前遍历节点
cur.next = odd.next;
cur = cur.next;
}
//奇偶头尾拼接
odd.next = even;
return head;
};
15.删除有序链表中重复的元素-1
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
示例:
输入:{1,1,2,3,3,4,4}
返回值:{1,2,3,4}
输入:{1,1,1}
返回值:{1}
方法1 :如果pre和cur的值相等,则pre则不断跳过cur,指向cur的下一个节点
function deleteDuplicates( head ) {
// write code here
if(!head){
return null;
}
let res = new ListNode(200);
res.next = head;
let pre = res;
let cur = head;
while(cur){
while(cur && pre.val===cur.val){
pre.next = cur.next;
cur = cur.next;
}
if(cur){
pre = cur;
cur = cur.next;
}
}
return res.next;
}
leetcode上清晰的写法
function deleteDuplicates( head ) {
// write code here
if (!head) {
return head;
}
let cur = head;
while (cur.next) {
if (cur.val === cur.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
16.删除有序链表中重复的元素
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
示例:
输入:{1,2,3,3,4,4,5}
返回值:{1,4,5}
方法1:让pre一直都指向中间有相等节点的前一个节点,cur不断跳过相同值的节点,最终让让pre指向cur
function deleteDuplicates( head ) {
// write code here
let cur = head;
let res = new ListNode(1001);
res.next = head;
let pre = res;
while(cur && cur.next){
if(cur && cur.next && cur.val === cur.next.val){
while(cur && cur.next && cur.val === cur.next.val){
cur = cur.next;
}
cur = cur.next;
pre.next = cur;
}
if(cur && cur.next && cur.val !== cur.next.val){
pre = cur;
cur = cur.next;
}
}
return res.next;
}
leetcode上的代码
var deleteDuplicates = function(head) {
if (!head) {
return head;
}
const dummy = new ListNode(0, head);
let cur = dummy;
while (cur.next && cur.next.next) {
if (cur.next.val === cur.next.next.val) {
const x = cur.next.val;
while (cur.next && cur.next.val === x) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummy.next;
};