一周力扣刷题笔记
Posted 撒冷才不是小小蒙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一周力扣刷题笔记相关的知识,希望对你有一定的参考价值。
一周力扣刷题笔记(10月31日)
这周的刷题主要是针对于栈的内容进行的,也包括了一些对于链表知识的整理与回顾
旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
这道题最开始的基本思路是分成两部分操作,向右移动的话,只要将后面k个位置的节点移动到头节点,就可以了。但这种方法的边界条件过多,而且由于要多次操作整个链表,因此要定义多个头指针来遍历数组,非常麻烦。最重要的是,当K远于链表长度,这种方式是无法处理的(失败版本)
var rotateRight = function(head, k) {
let len = 0;
let len2 = 0;
let res = head;
let res2 = head;
if (head == null || head.next == null) { return head }
if (k < 2) { return head }
while (res2.next != null) {
len2++;
res2 = res2.next;
}
len2++;
k = k % len2;
while (len != k) {
len++;
res = res.next
}
let temp = res.next;
res.next = null;
let tem = temp
while (tem.next != null) {
tem = tem.next
}
tem.next = head;
return temp
}
更好的做法是采用循环链表的方式,直接重新定义头节点,利用环形链表的特定,直接将已经遍历到为尾部的指针重新连接到头部,通过操作指针的方式,找到移动过后的头节点,再进行分割,就达到了后移的目的,同时,利用环形链表的节点数可以取余也有效的解决了K值大于链表长的问题
var rotateRight = function(head, k) {
if (k === 0 || !head || !head.next) {
return head; //除去边界条件
}
let n = 1;
let cur = head;
// 先遍历出链表的长度
while (cur.next) {
cur = cur.next;
n++;
}
let add = n - k % n; //注意环形链表
// 移动整数倍直接返回头节点
if (add === n) {
return head;
}
// 使链表称为一个环形链表
cur.next = head;
//将节点后移,利用环形链表直接后移
while (add) {
cur = cur.next;
add--;
}
const ret = cur.next;
// 利用cur.next=null将环形链表分割
cur.next = null;
return ret;
};
两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
这道题的难度并不大,主要是要理清楚交换两个节点的顺序,对于单向链表而言,由于只能访问大后面的节点,因此操作节点一定要从后往前进行操作,防止出现节点丢失的问题
var swapPairs = function(head) {
let headnode = new ListNode(); //重新定义一个空的头节点用于辅助
headnode.next = head;
let res = headnode;
console.log(res)
while (res.next != null && res.next.next != null) { //(temp.next && temp.next.next) 条件可以写成这种形式
let tmp = res.next.next; //先保存后一位的节点
res.next.next = res.next.next.next; //将前节点的next连接到后一位节点的next
tmp.next = res.next; //将后节点放到前节点之前
res.next = tmp; //将后一位节点连接到前节点的前一位(辅助节点的下一位)
res = res.next.next; //指针往后移动两位(每次操作了两位)
}
return headnode.next;
};
删除排序链表中的重复元素
** 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。**
这道题目相对比较简单,基本就是一般的遍历循环并判断,但要注意的一点是,在这道题里如果发生了节点删除,指针是不移动到下一位的,否则当出现连续的重复元素时,指针在第一次删除元素后跳到了剩余的相同元素的下一位,相当于跳过了一个相同元素,最后就会出现重复元素消除不彻底的情况
var deleteDuplicates = function(head) {
let res = head;
while (1) {
if (!res || !res.next) { break; }
if (res.next.val == res.val) {
res.next = res.next.next;
} else { //注意这里要有else,否则多个连续的重复数字无法去重
res = res.next;
}
}
return head;
}
删除排序链表中的重复元素2
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。 返回同样按升序排列的结果链表。
跟前面的题目类似,但难度明显大了很多,这道题要求去掉所有链表中发生过重复的元素,这里不仅仅是要多考虑后面一个节点,还要考虑连续重复的节点需要一直舍弃的问题,因此当出现重复元素时先保存这个值,然后一直往后遍历直到后面的值与这个保存的值不相等为止,在这个过程中迭代指针去掉所有重复的元素(指针最后指向的是不重复的元素)
var deleteDuplicates = function(head) {
let headnode = new ListNode();
headnode.next = head;
let res = headnode;
let las = res;
let temp;
if (!res.next) { return res.next }
while (true) {
if (!res.next.next) { break; } //后面只有一个元素必不可能重复
// 这里要跟下面对应起来,会进行贪婪去重,当循环结束时指针以及指针前面一定没有重复元素
if (res.next.val == res.next.next.val) {
temp = res.next.val;
while (res.next.val == temp) {
res.next = res.next.next;
// 循环指针直到没有重复元素为止,注意此时最后最后一次去完后,后面哪一项一定不与前面的重复
if (!res.next) { return las.next }
}
} else {
res = res.next;
// 继续指向下一个节点
}
}
return las.next;
};
分割链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。
这道题我最开始的基本思路是,由于只要让所有小的出现在一边,大的出现在另一边,那么在一次遍历时,定义两个链表,一个存放比x小的所有节点,另一个存放比x大的所有节点,最后将两个链表和x拼接起来就可以了。
var partition = function(head, x) {
let onehead = new ListNode(0);
onehead.next = head;
head = onehead;
let res = head;
let lastnode = new ListNode(0);
let last = lastnode;
let headnode = new ListNode(0);
let bef = headnode;
while (1) {
if (res.val == x) {
last.next = null;
break;
}
if (res.next.val > x) {
last.next = res.next;
last = last.next;
res.next = res.next.next;
}
res = res.next
}
while (1) {
if (!res.next) { break; }
if (res.next.val < x) {
bef.next = res.next;
bef = bef.next;
res.next = res.next.next;
} else {
res = res.next
}
}
head = head.next;
lastnode = lastnode.next;
bef.next = head;
res.next = lastnode;
return headnode.next
};
这种方式是能够在多数情况符合要求的,但少数情况下会由于这个代码保留了x两边的节点,导致一些时候没有保留相对位置,其实正确的解法跟我的思路基本是一样的,但他仅仅拆分成立小于x和大于等于x的部分,在简化代码的同时也保留了相对位置,防止了边界条件。同时,下面这个程序巧妙地运用了=从右往左计算的性质,只用一个指针遍历了一遍链表,效率更高
var partition = function(head, x) {
let pA = a = new ListNode(0),
pB = b = new ListNode(0) //利用等号处理简化代码
while (head) {
head.val < x ? a = a.next = head : b = b.next = head
//利用等号的处理顺序简化代码
head = head.next
}
a.next = pB.next
b.next = null //注意将后面链表的next置空
return pA.next
};
所有显然我考虑问题还不够周全,差了一点点
##简化路径
给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径。
-
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,’//’)都被视为单个斜杠 ‘/’ 。 对于此问题,任何其他格式的点(例如,’…’)均被视为文件/目录名称。
-
请注意,返回的 规范路径 必须遵循下述格式:
-
始终以斜杠 ‘/’ 开头。
-
两个目录名之间必须只有一个斜杠 ‘/’ 。
-
最后一个目录名(如果存在)不能 以 ‘/’ 结尾。
*此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ‘.’ 或 ‘…’)。 -
返回简化后得到的 规范路径 。
这道题明显是要我们使用栈进行解题,按照linux的规则,遇到文件名进栈;遇到’…/‘出栈,遇到’./'直接继续遍历,注意文件名是多个字符串,要先对字符串数组做一些处理,转化为文件名和特殊符号的形式,最后还有对边界条件进行处理
var simplifyPath = function(path) {
path = path.split("");
if (path[path.length - 1] != '/') { path.push('/') }
let arr = [];
let stk = [];
let str = '';
// 先对数组进行处理,将其转变为/与文件名/特殊符号的形式
for (let i = 0; i < path.length; i++) {
if (path[i] == '/') {
if (str != '') { //注意文件名是多个字符,需要先保存再一并 加入
arr.push(str);
str = '';
}
continue;
} else {
str += path[i];
}
}
console.log(arr)
for (let i = 0; i < arr.length; i++) {
if (arr[i] != "." && arr[i] != '..') {
stk.push(arr[i]); //不等于特殊符号默认文件名,直接进栈
} else if (arr[i] == ".") {
continue; //遇到'./'同级文件,跳过
}
if (arr[i] == '..' && stk != []) {
stk.pop(); //遇到../出栈,注意判断非空条件
}
}
// 最后加入/并对边界条件做一些处理
let las = stk.join("/").split("");
if (las[0] != '/') { las.unshift("/") }
return las.join("")
};
这个算法还可以优化,比如处理字符串的那一步,我们可以直接通过split这个API进行处理,对循环和边界条件的处理的写法也可以化简
const dir = path.split('/'),
stack = []
for (const i of dir) {
// 特殊情况直接跳过
if (i === '.' || i === '') continue
// 遇到'..'入栈
if (i === '..') {
stack.length > 0 ? stack.pop() : null
continue
}
//其他情况直接进栈
stack.push(i)
}
// 直接在return处处理结果
return '/' + stack.join('/')
有效的括号
** 给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。**
-
有效字符串需满足:
-
左括号必须用相同类型的右括号闭合。
-
左括号必须以正确的顺序闭合。
还是经典的栈的运用,不管我们右多少括号,处理的时候只要处理栈顶就可以了,如果匹配到左括号就入栈,匹配到右括号就跟栈顶进行比较,如果跟栈顶的左括号无法匹配,就说明匹配失败,这组括号是非法的。否则就将栈顶出栈,如果最后栈空,说明一一匹配完毕,括号组合法。
对于代码的编写,我们可以选择使用map进行匹配,或者暴力else if进行配对判断,但这里我们可以灵活使用栈,但匹配到左括号时,让对应的右括号入栈,这样当匹配到右括号时只需要比较右括号跟栈顶的元素是否相等,这种写法大大减少了我们的代码量
var isValid = function(s) {
const stack = [];
for (let val of s) {
console.log(stack)
if (val === '(') stack.push(')');
else if (val === '[') stack.push(']');
else if (val === '{') stack.push('}');
else if (stack.length === 0 || val !== stack.pop()) return false;
}
return stack.length === 0;
};
最长有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
跟上一道题很相似,但这里要返回最长的字串长度。最开始我的思路是设置两个栈,当匹配到左括号时不断入其中一个栈(常栈),当匹配到右括号时,常栈出栈并将出栈的元素push进另一个栈(临时栈),记录临时栈的长度,当常栈匹配的过程种发现无法匹配到有效括号时,临时栈清,重新添加并挑战最大长度。代码实现如下:
var longestValidParentheses = function(s) {
let stk = [];
let max = 0;
let temp = [];
s = s.split("");
for (let i = 0; i < s.length; i++) {
if (stk.length == 0 && s[i] == ")") {
console.log(temp)
temp = [];
continue;
}
if (s[i] == "(") {
stk.push(s[i]);
}
if (stk.length != 0 && s[i] == ")") {
temp.push(stk[stk.length - 1]);
temp.push(s[i]);
stk.pop();
if (temp.length > max) {
max = temp.length;
}
}
}
return max;
}
但这种算法存在问题,先不考虑开辟两个栈的内存消耗,如果出现像"(()"这种"最长有效括号在无效括号中"的情况,这种方法会匹配失败。
正确的解决方法是,通过保存下标并与起始下标相减来获得最大长度,基本原则是匹配到左括号进栈,匹配到右括号时,先出栈,如果栈非空,就通过下标计算长度并挑战最大长度,如果栈空,说明之前已经匹配完一组合法的括号,且新push进来的这个是不合法的,所有可以将他push进栈的第一项作为新的初始项,这里要注意一下初始项,由于下标是从0开始的,而且可能出现上面这种栈空进右括号的情况,所有我们需要定义一个初始值(-1)作为下标的第一位,之后出现上面的情况,就通过让右括号下标进栈的方式重置这个下标
var longestValidParentheses = function(s) {
let stk = [];
let max = 0;
stk.push(-1);
for (let i = 0; i < s.length; i++) {
if (s[i] == "(") {
stk.push(i);
} else {
stk.pop();
if (stk.length != 0) {
max = Math.max(max, i - stk[stk.length - 1]);
} else {
stk.push(i)
}
}
}
return max;
};
二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
基础题,不做过多解释
var inorderTraversal = function(root) {
if (!root) { return [] }
let res = [];
allTree(root);
function allTree(node) {
if (node.left) {
allTree(node.left);
}
res.push(node.val);
if (node.right) {
allTree以上是关于一周力扣刷题笔记的主要内容,如果未能解决你的问题,请参考以下文章