文章简要概述
-
本篇主要是复习链表相关的内容,部分算法题之前可能写过,这里进行回顾,进一步加深印象。
刷题不是目的,目的是理解与掌握。 -
本文一共有4道题,主要介绍leetcode中
分隔链表、面试题 02.04. 分割链表、第K个语法符号和剑指 Offer 10- I. 斐波那契数列的解题思路。
与链表相关算法
分隔链表
题目大意:
给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。
这 k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。
返回一个由上述 k 部分组成的数组。
示例
输入:head = [1,2,3,4,5,6,7,8,9,10], k = 3
输出:[[1,2,3,4],[5,6,7],[8,9,10]]
解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过 1 。前面部分的长度大于等于后面部分的长度。
解题思路:
- 注意事项:链表数不够分隔时填充空数据,分隔链表后每个部分的长度不超过1
- 先获取链表的长度
l,计算l/k,得到分隔后链表的长度。如果l/k得到的不是整数,那么我们记录l%k的值remainder,让分隔的链表前remainder的部分多1个值 - 然后遍历链表,按照上面的规则,切割链表。
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode[]}
*/
var splitListToParts = function(head, k) {
let l = 0;
let node = head;
while(node) {
node = node.next;
l++;
}
const res = new Array(k).fill(null);
const remainder = l % k;
const quantity = Math.floor(l/k);
let cur = head;
for(let i = 0; cur; i++) {
res[i] = cur;
const pageSize = quantity + (i < remainder ? 1 : 0)
for(let j = 1; j < pageSize; j++) {
cur = cur.next;
}
const next = cur.next;
cur.next = null;
cur = next;
}
return res;
};
面试题 02.04. 分割链表
题目大意:
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
示例:
输入: head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
解题思路:
- 设置两个链表,分别存链表中小于x的值
small和大于等于x的值large。 - 然后将两个链表链接。
代码:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} x
* @return {ListNode}
*/
var partition = function(head, x) {
let smallList = new ListNode(-1);
const smallHead = smallList;
let largeList = new ListNode(-1);
const largeHead = largeList;
while(head) {
if (head.val < x) {
smallList.next = head;
smallList = smallList.next;
} else {
largeList.next = head;
largeList = largeList.next
}
head = head.next;
}
largeList.next = null;
smallList.next = largeHead.next;
return smallHead.next;
};
第K个语法符号
题目大意:
在第一行我们写上一个 `0`。接下来的每一行,将前一行中的`0`替换为`01`,`1`替换为`10`。
给定行数 `N` 和序数 `K`,返回第 `N` 行中第 `K`个字符。(`K`从1开始)
示例:
输入: N = 1, K = 1 输出: 0
输入: N = 2, K = 1 输出: 0
输入: N = 2, K = 2 输出: 1
输入: N = 4, K = 5 输出: 1
解释:
第一行: 0
第二行: 01
第三行: 0110
第四行: 01101001
解题思路:
- 可以分为两种情况
- 如果K在前半段,所对应的值就是上一行的第K个值
- 如果K在后半段,可以先算出K相对于后半段的位置,然后找出上一行这个位置的值,把值反过来
代码:
/**
* @param {number} n
* @param {number} k
* @return {number}
*/
var kthGrammar = function(n, k) {
if (n === 1) return 0;
const l = Math.pow(2, n-1);
if (k > l/2) {
const val = kthGrammar(n-1, k-l/2);
return val === 1 ? 0 : 1;
}
return kthGrammar(n-1, k);
};
剑指 Offer 10- I. 斐波那契数列
剑指 Offer 10- I. 斐波那契数列 -- leetcode
题目大意:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例:
输入: n = 2
输出: 1
解题思路:
- 本题有动态规划的求解方法,留作后面动态规划练习时实现,感兴趣的可以看leetcode中的解答。
矩阵乘法案例
- 只要我们能快速计算矩阵
M 的 n次幂,就可以得到F(n)的值。 - 如果直接求取
M^n,时间复杂度是O(n),可以定义矩阵乘法,然后用快速幂算法来加速这里M^n的求取。
代码:
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
if (n < 2) {
return n;
}
const q = [[1, 1], [1, 0]];
const res = pow(q, n - 1);
return res[0][0];
};
const pow = (a, n) => {
let ret = [[1, 0], [0, 1]];
while (n > 0) {
if ((n & 1) === 1) {
ret = multiply(ret, a);
}
n /=2;
a = multiply(a, a);
}
return ret;
}
const multiply = (a, b) => {
const c = new Array(2).fill(0).map(() => new Array(2).fill(0));
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
c[i][j] = (BigInt(a[i][0]) * BigInt(b[0][j]) + BigInt(a[i][1]) * BigInt(b[1][j])) % BigInt(1000000007);
}
}
return c;
}
结束语
数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是链表复习部分。后期还会有其他类型的数据结构,题目来源于leetcode。往期文章:
链表相关算法复习 数据结构与算法-栈一 数据结构与算法-栈二
数据结构与算法-队列一 数据结构与算法-队列二 数据结构与算法-链表一
有兴趣的可以一起来刷题,感谢点赞👍 , 关注!