【路飞】设计链表&&根据val删除链表节点&&平均地分隔链表

126 阅读4分钟

记录 3 道算法题

设计链表

leetcode-cn.com/problems/de…


设计一个链表,用双向链表实现,有索引,跟数组很像。提供 get,addAtHead,addAtTail, addAtIndex,deleteAtIndex方法。分别用于获取某个index的节点的val,插入到链表的开头,插入到链表的结尾。插入到index节点的前面,删除index节点。

用到了伪节点,这样添加起来很方便。不用做很多 if 判断。

因为要排除伪节点,所以index = 0 是 this.head.next, index = this.length - 1 是 this.tail.prev

    function Node(val, next = null, prev = null) {
        this.next = next
        this.prev = prev
        this.val = val
    }
    function MyLinkedList() {
        this.length = 0
        this.head = new Node(0)
        this.tail = new Node(0)
        this.head.next = this.tail
        this.tail.prev = this.next
    }
  1. get方法

    因为使用了双向链表,所以可以根据开头和结尾两边找最近的。还要做判断index是否正确。

        MyLinkedList.prototype.get = function(index) {
            // 检查index在不在范围内
            if (this.length - 1 < index || index < 0) return -1
    
            let node
            // 如果是中间偏后的就从后面找,如果是偏前就在前面找
            if (index < this.length / 2) {
                node = this.head.next
                while(index > 0) {
                    node = node.next
                    index--
                }
            } else {
                node = this.tail.prev
                index = this.length - 1 - index
                while(index > 0) {
                    node = node.prev
                    index--
                }
            }
    
            return node.val
        }
    
  2. addAtHead

    从头插入分为两个可能,第一是当链表里没有东西的时候,要插在 tail 的前面。链表里面有节点了,就是插入在 this.head.next 前面。

    然后可以归纳为插入到 index = 0 的地方。所以可以使用 addAtIndex。但是这样的话就需要在 addAtIndex 里面判断链表的长度是不是0。

        MyLinkedList.prototype.addAtHead = function (val) {
            this.addAtIndex(0, index)
        }
    
  3. addAtHead

    和上面同理也可以使用 addAtIndex ,不同的是不需要考虑链表长度是不是为 0。

        MyLinkedList.prototype.addAtTail = function(val) {
            this.addAtIndex(this.length, val)
        }
    
  4. addAtIndex

    重头戏来了。每一个插入动作都是先生成一个节点,next指向index的节点,prev指向index的前一个节点,然后再让 index的节点和它前面的节点断开联系。所以只要找得到 index的节点就成功了。

        MyLinkedList.prototype.addAtIndex = function(index, val) {
            // 超过了链表长度就无法插入
            if (index > this.length) return
            let node
            // 根据题目要求,小于0的都插入到开头。
            if (index <= 0) {
                // 这里就是前面说的 如果没有节点时 插入到开头的判断
                if (this.length === 0) {
                    this.head.next = new Node(val, this.head.next, this.head)
                    this.head.next.next.prev = this.head.next
                    this.length++
                    return
                  }
                node = this.head.next
            } else if (index === this.length) {
                // 在如果是最后一个的索引+1,那就是插入到结尾
                node = this.tail
            } else {
                // 和get一样的找到那个节点
                if (index < this.length / 2) {
                    node = this.head.next
                    while(index > 0) {
                        node = node.next
                        index--
                    }
                } else {
                    node = this.tail.prev
                    index = this.length - 1 - index
                    while(index > 0) {
                        node = node.prev
                        index--
                    }
                }
            }
            // 新节点指向 next 和 prev
            // 然后重置 next 和 prev 的联系
            node.prev = node.prev.next = new Node(val, node, node.prev)
            // 记得加1
            this.length++
        };
    
  5. deleteAtIndex

    找到 index 的节点然后直接让 prev.next = next就行了。

        MyLinkedList.prototype.deleteAtIndex = function(index) {
            // 前面和 get 一样的
            if (this.length - 1 < index || index < 0) return
            let node
            if (index < this.length / 2) {
                node = this.head.next
                while(index > 0) {
                    node = node.next
                    index--
                }
            } else {
                node = this.tail.prev
                index = this.length - 1 - index
                while(index > 0) {
                    node = node.prev
                    index--
                }
            }
            
            // 删除节点
            node.prev.next = node.next
            node.next.prev = node.prev
            this.length--
        }
    

根据val删除链表的节点

leetcode-cn.com/problems/sh…


删除节点都是一样的方法,先找到要删除节点的前一个节点。所以需要比较 node.next.val === val

因为是比较 node.next.val 所以需要先手动比较一下第一个节点 node.val

    function deleteNode(head, val) {
        let node = head
        // 先解决第一个节点
        if (node.val === val) {
          return head.next
        }
        
        // 找到前一个节点
        while (node?.next) {
          if (node.next.val === val) {
            break
          } else {
            node = node.next
          }
        }
        
        // 如果存在就删掉
        if (node.next) {
          node.next = node.next.next
        }

        return head
   }

平均分隔链表

725. 分隔链表 - 力扣(LeetCode) (leetcode-cn.com)


要求:

    * 平均地将链表分成 k 组,组与组之间相差的个数不能超过 1。
    * 前面的组的个数不能比后面的组小
    * 分隔的节点放进数组输出

对于分组的做法。就是简单的让要分隔的节点斩断联系。node.next = null

如果分组的个数大于了链表的长度,就将链表切成一个个,然后剩余的位置push null。

怎么说也得先遍历一遍链表,得到节点个数,然后就可以除以 k,而多出来的节点则是和 k 取余。然后把取余的结果分成一次次加到前面的分组。

然后用双循环,外循环处理分组的push, 内循环处理分组的内容。要注意的点是如何在内循环获取足够数量的节点。然后斩断联系,保存下一组的开头。

处理链表永远要处理怎么保存被斩断的节点。

    function splitListToParts(head, k) {
        let count = 0
        let node = head
        // 统计节点的个数
        while (node) {
          node = node.next
          count++
        }

        node = head
        const output = []
        // 如果分组数量大 那就拆成一个节点一组
        if (k >= count) {
          for (let i = 0; i < k; i++) {
            if (node) {
                // 保存被斩断的节点
              const temp = node.next
              node.next = null
              output.push(node)
              node = temp
            } else {
                // 补空位
              output.push(null)
            }
          }

          return output
        }

        // 平均数向下取整
        let n = (count / k) | 0
        // 平均分后多出来的节点数量
        count = count % k

        for (let i = 0; i < k; i++) {
            // 用 head 标记每一组的开头,最后push进数组
          head = node
          // 要 n - 1,即每一组个数 - 1,
          // 因为我们需要让他停在每一组最后一个节点
          for (let j = 0; j < n - 1; j++) {
            node = node.next
          }

            // 如果还有多余的次数就分配一个,给当前的组
          if (count) {
            node = node.next
            count--
          }
          output.push(head)
          // 先保存下一组的开头
          head = node.next
          // 斩断联系
          node.next = null
        }

        return output
    }