【算法】01.链表

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个两个无环的单链表,且是有公共节点的  

在这里插入图片描述
题解: 两个同频的指针l1l2,如果存在公共节点,则如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;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值