[路飞]_今夜算法时刻

350 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

目标

  1. 从尾到头打印链表 leetcode-cn.com/problems/co…
  2. 返回倒数第 k 个节点 leetcode-cn.com/problems/kt…
  3. 环形链表 leetcode-cn.com/problems/li…
  4. K 个一组翻转链表 leetcode-cn.com/problems/re…
  5. 只出现一次的数字 leetcode-cn.com/problems/WG…

剑指 Offer 06. 从尾到头打印链表

简单归简单,目标既然定下,就不能因为简单而忽略;
思路清晰,代码如下

var reversePrint = function(head) {

    // 用数据存储答案
    const result = [];
    while(head){
        // 将当前值放在数组头部
        result.unshift(head.val)
        head = head.next;
    }
    // 返回数组
    return result
};

面试题 02.02. 返回倒数第 k 个节点

思路:
因为链表特性,只能一步一步向链表尾部查找,且只能有头节点向尾节点寻找;怎么处理这类查找倒数第几个节点的问题呢?
第一种方法:
根据剑指 Offer 06. 从尾到头打印链表 这题的思路,我们可以将链表放在数组中;且将链表中的数据倒着放在数组中,这样直接返回数组第k-1位不就是倒数第k位节点吗?

根据上述代码书写代码如下:

  var kthToLast = function(head,k){
      let root = head;
      let list = []
      while(root){
        list.unshift(root.val)
        root = root.next 
      }
     return list[k-1]
  }

分析上述代码:
时间复杂度是O(n);
空间复杂度也是O(n);

有没有更优的解题思路呢?

第二种方法:

首先使用两个分别为fast和slow指针分别指向链表头部;
先让fast指针向链表尾部运行,运行多少步呢?运行k步;

当fast指针向前运行k步后,让slow指针也向前运行,这样fast和slow指针相差位k;

这是当fast指针指向head链表的尾部, slow当前所在的位置就是head到处第k位;

根据上述思路书写代码如下

  var kthToLast = function(head,k){
      let fast = head;
      let slow = head;
      while(k--){
          fast = fast.next;
      }
      while(fast){
          slow = slow.next
          fast = fast.next
      }
      return slow.val

  }

141. 环形链表

第一种方法

首先想到哈希表;遍历链表,将节点放在数组中,如果数组中意境存在该节点,表示链表有环,否则无环;

const hasCycle = function(head) {
  const res = [];
  while (head) {
    if (res.includes(head)) {
      return true;
    }
    res.push(head);
    head = head.next;
  }
  return false;
};

第二种方法

快慢指针 首先使用两个分别为fast和slow指针分别指向链表头部;

fast指针每次向链表尾部走两步;

slow指针每次向链表尾部走一步;

结果:

如果fast能走到链表尾部或者fast为null 或者fast.next 为 null;链表一定没有环;

如果链表有环,哪一定有fast === slow的时候;

根据上述思路编辑代码如下:

var hasCycle = function (head) {
  if (head === null || head.next === null) return false
  let fast = head.next
  let slow = head
  while (fast !== null && fast.next !== null) {
    if (fast === slow) return true
    fast = fast.next.next
    slow = slow.next
  }
  return false
}

25. K 个一组翻转链表

核心思路

反转链表;

将当前反转后的链表表头链接到上一个反转链表节点表尾;
将当前反转后的链表表尾链接下一个反转链表的表头;
pre表示链表上一个节点指针
current表示链表当前节点指针
next表示链表下一个节点指针


//K 个一组翻转链表

var reverseKGroup = function (head, k) {
  let header = new ListNode(0)
  header.next = head
  let node = head

  // 获取整个链表长度
  let len = 0
  while (node) {
    len++
    node = node.next
  }

  

  //上一节点
  let pre = header

  // 当前节点
  let current = header.next

  // 下一节点
  let next = null

    // 再次遍历链表,当len小于k时不需要反转链表
  while (len >= k) {
    //反转0-k之间的链表
    for (let i = 0; i < k - 1; i++) {
      // 下一节点等于当前节点的下一节点;
      next = current.next
      // 将开始节点的下一节点指向下一节点的下一节点,意味着删除了next这个节点
      current.next = next.next
      next.next = pre.next
      pre.next = next
    }
    pre = current
    current = pre.next
    len -= k
  }

  return header.next
}

剑指 Offer II 004. 只出现一次的数字

方法1

一个比较容易想到的方法;哈希表,将出现的数字统计带哈希表中,遍历哈希表;

根据上述思路编辑代码可以轻易完成本题;代码如下:

var singleNumber = function(nums) {
    const map = {};
    nums.forEach(v=>{
        map[v] = (map[v]||0)+1
    })
   return  Object.keys(map).filter(v=>map[v] === 1)[0]
};

这题还有个进阶问题,要求不使用额外空间来实现吗;

这就有点难了,必须一次遍历即可知道答案;哈希表肯定是不行了;必须另向别的办法;

可以巧用#### 260. 只出现一次的数字 III的思想;260解题思路详见

将数组中的数据区分出来;

想想,其他数据出现3次,目标数据出现1次;
假设将数组中数据转为为二进制; 假设数据中出现1次的是数据为x;出现3次的数据为y;

将x,和y转换为二进制;

对于二进制任意一位;如果y在位置为1,3个y向加这时的二进制位置是不是就是3,那将这个位置再加上x在这个位置的数值;

10进制
5101
5101
5101
3011
二进制位之和314
二进制位之和余3011

上述图表就比较清晰了吧; 根据上述思路编辑代码如下:

var singleNumber = function(nums) {
    let result = 0;
    for(let i = 0 ; i < 32 ; i++){
        let temp = 0;
        for(let j = 0 ; j < nums.length ; j++){
            // 右移i位,并且通过&1得到第i位是0还是1
            temp = temp + ((nums[j] >> i) & 1);
        }
        if(temp %3 != 0){
        //这里因为二进制位只可能是1或者0;如果为1,左移i位可以得到10进制数;
        //result 累加得到是10进制数
            result +=  (1<< i)
        }

    }
    // 返回答案
    return result;
};