数组
704 二分查找
链接:https://leetcode.cn/problems/binary-search/ 条件:不重复的有序数组中查找目标值 搜索过程从数组的中间元素开始;如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找 注意在闭区间[left, right]中查找,left==right时仍有意义
var search = function ( nums, target ) {
let left = 0 , right = nums. length - 1 ;
while ( left <= right) {
mid = left + Math. floor ( ( right - left) / 2 ) ;
if ( target == nums[ mid] ) {
return mid;
} else if ( target > nums[ mid] ) {
left = mid + 1 ;
} else {
right = mid - 1 ;
}
}
return - 1 ;
} ;
59.螺旋矩阵II
链接:https://leetcode.cn/problems/spiral-matrix-ii/ 给出正整数n,生成1–n^2的螺旋矩阵 js初始化n*n数组: new Array(n).fill(0).map(()=>new Array(n).fill(0));
如果n为奇数,矩阵有Math.floor(n/2)圈,并将中间元素填充为n^2 如果n为偶数,矩阵有Math.floor(n/2)圈 每一圈设为一个循环,在循环内分别填充四条边,左闭右开 设置startx,starty为每一圈起始坐标(左上角),每圈加一 设置n - offset为每条边向右/向下的终止位置,由于左闭右开,offset初始为1,每圈加一 j标记横向坐标,i标记纵向坐标
var generateMatrix = function ( n ) {
let startx = 0 , starty = 0 , offset = 1 , count = 1 ;
let loop = Math. floor ( n/ 2 ) ;
let matrix = new Array ( n) . fill ( 0 ) . map ( ( ) => new Array ( n) . fill ( 0 ) ) ;
if ( n % 2 == 1 ) {
matrix[ loop] [ loop] = n * n;
}
while ( loop) {
let j = startx, i = starty;
for ( j = startx; j < n - offset; j++ ) {
matrix[ starty] [ j] = count;
count ++ ;
}
for ( i = starty; i < n - offset; i++ ) {
matrix[ i] [ j] = count;
count ++ ;
}
for ( ; j > startx; j-- ) {
matrix[ i] [ j] = count;
count ++ ;
}
for ( ; i > starty; i-- ) {
matrix[ i] [ startx] = count;
count ++ ;
}
startx ++ ;
starty ++ ;
offset ++ ;
loop -- ;
}
return matrix;
} ;
链表
JS定义链表
class ListNode {
val;
next = null ;
constructor ( value ) {
this . val = value;
this . next = null ;
}
}
203.移除链表元素
链接:https://leetcode.cn/problems/remove-linked-list-elements/ 为了统一移除头结点和其他结点的操作,设置虚拟头指向现有链表头 由于检查的是current.next节点的值,在每次删除操作之后不需要后移当前结点,而是继续检查新的current.next节点 返回虚拟头指向的结点,即为真正的新头结点
var removeElements = function ( head, val ) {
const dummyHead = new ListNode ( 0 , head) ;
let current = dummyHead;
while ( current. next != null ) {
if ( current. next. val === val) {
current. next = current. next. next;
} else {
current = current. next;
}
}
return dummyHead. next;
} ;
707.设计链表
https://leetcode.cn/problems/design-linked-list/ 设计并实现一个链表
实现节点类和链表对象,节点类使用值和next指针构造一个节点,链表对象有头指针、尾指针和size属性。
class ListNode {
constructor ( value, next ) {
this . value = value;
this . next = next;
}
}
var MyLinkedList = function ( ) {
this . size = 0 ;
this . head = null ;
this . tail = null ;
} ;
get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,返回 -1 。 使用cur初始指向头结点,index=0时不后移。
MyLinkedList . prototype. get = function ( index ) {
if ( index > this . size - 1 || index < 0 ) {
return - 1 ;
}
let cur = this . head;
for ( let i= 0 ; i< index; i++ ) {
cur = cur. next;
}
return cur. value;
} ;
addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。 创建新节点,移动头指针,如果尾指针为空(原链表为空),则将尾指针指向新节点。
MyLinkedList . prototype. addAtHead = function ( val ) {
const newNode = new ListNode ( val, this . head) ;
this . head = newNode;
this . size ++ ;
if ( ! this . tail) {
this . tail = newNode;
}
} ;
addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。 如果尾指针为空(原链表为空),则头尾指针均指向新节点;如果原链表不为空,则将原尾指针的next指向新节点,然后修改尾指针。
MyLinkedList . prototype. addAtTail = function ( val ) {
const newNode = new ListNode ( val, null ) ;
this . size ++ ;
if ( ! this . tail) {
this . tail = newNode;
this . head = newNode;
} else {
this . tail. next = newNode;
this . tail = newNode;
}
} ;
addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
先检查index,index无效,返回;index 等于链表的长度,调用addAtTail,返回;index为0,调用addAtHead,返回。 对于插入到链表中间的结点,使用cur找到index的前一个节点,将新节点插入到cur所指节点之后。
MyLinkedList . prototype. addAtIndex = function ( index, val ) {
if ( index > this . size || index < 0 ) {
return ;
} else if ( index == this . size) {
this . addAtTail ( val) ;
return ;
} else if ( index === 0 ) {
this . addAtHead ( val) ;
return ;
}
let cur = new ListNode ( 0 , this . head) ;
let newNode = new ListNode ( val, null ) ;
for ( let i= 0 ; i< index; i++ ) {
cur = cur. next;
}
newNode. next = cur. next;
cur. next = newNode;
this . size ++ ;
} ;
deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
先检查index,index无效,返回;index为0,修改当前头结点指向下一个,如果删除后链表为空,修改尾节点为空,返回。 如果删除的不是头结点,也不是链表的唯一节点,使用cur从虚拟头结点开始遍历链表到index的前一个结点,删除该节点; 如果删除的是尾节点,修改尾节点指向。
MyLinkedList . prototype. deleteAtIndex = function ( index ) {
if ( index >= this . size || index < 0 ) {
return ;
}
if ( index === 0 ) {
this . head = this . head. next;
if ( index === this . size - 1 ) {
this . tail = null ;
}
this . size -- ;
return ;
}
let cur = new ListNode ( 0 , this . head) ;
for ( let i = 0 ; i < index; i++ ) {
cur = cur. next;
}
cur. next = cur. next. next;
if ( cur. next === null ) {
this . tail = cur;
}
this . size -- ;
} ;
206.反转链表
链接:https://leetcode.cn/problems/reverse-linked-list/ 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
双指针法:将每个结点的next指针指向前一个结点。定义cur指向头结点,pre指向前一个结点,temp在cur改变指向后暂存下一个结点。
var reverseList = function ( head ) {
let pre = null , temp = null ;
let cur = head;
while ( cur) {
temp = cur. next;
cur. next = pre;
pre = cur;
cur = temp;
}
return pre;
} ;
递归法
var reverse = function ( pre, cur ) {
if ( ! cur) return pre;
let temp = cur. next;
cur. next = pre;
return reverse ( cur, temp) ;
}
var reverseList = function ( head ) {
return reverse ( null , head) ;
} ;
反转奇数位置的链表
var reverseList = function ( head ) {
if ( ! head || ! head. next) return head;
let oddDummy = new ListNode ( 0 ) ;
let evenDummy = new ListNode ( 0 ) ;
let oddTail = oddDummy, evenTail = evenDummy;
let current = head;
let isOdd = true ;
while ( current) {
if ( isOdd) {
oddTail. next = current;
oddTail = oddTail. next;
} else {
evenTail. next = current;
evenTail = evenTail. next;
}
current = current. next;
isOdd = ! isOdd;
}
evenTail. next = null ;
oddTail. next = null ;
let prev = null ;
let cur = oddDummy. next;
while ( cur) {
let next = cur. next;
cur. next = prev;
prev = cur;
cur = next;
}
let reversedOddHead = prev;
let oddCurrent = reversedOddHead;
let evenCurrent = evenDummy. next;
let dummyHead = new ListNode ( 0 ) ;
let tail = dummyHead;
isOdd = true ;
while ( oddCurrent || evenCurrent) {
if ( isOdd && oddCurrent) {
tail. next = oddCurrent;
oddCurrent = oddCurrent. next;
} else if ( evenCurrent) {
tail. next = evenCurrent;
evenCurrent = evenCurrent. next;
}
tail = tail. next;
isOdd = ! isOdd;
}
return dummyHead. next;
} ;
142.环形链表II
链接:https://leetcode.cn/problems/linked-list-cycle-ii/
设置快指针fast每次走两个结点,慢指针slow每次走一个结点。如果快慢指针不会相遇,则链表无环。 头结点到环入口距离为x,入口到相遇点距离为y,相遇点再到入口距离为z。快慢指针相遇时,slow走了x+y,fast走了x+y+n*(y+z),由于fast速度是slow的2倍,相同步数下距离也为slow的2倍,即2(x+y) = x+y+n*(y+z); 推导可得,x = (n-1)*(y+z)+z。 根据上式,由于y+z为一圈,设置指针1从头结点出发,指针2从相遇结点出发,速度都为1,这两个指针会在指针2转了n-1圈后在入口相遇,此时将指针返回。
var detectCycle = function ( head ) {
let fast = head;
let slow = head;
while ( fast != null && fast. next != null ) {
fast = fast. next. next;
slow = slow. next;
if ( fast === slow) {
slow = head;
while ( fast !== slow) {
fast = fast. next;
slow = slow. next;
}
return slow;
}
}
return null ;
} ;
19. 删除链表的倒数第 N 个结点
链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/ 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 设置虚拟头结点,slow和fast指针初始指向虚拟头结点;为了删除倒数第n个结点,让fast先走n-1步,slow再开始走,当fast指向链表末尾时,slow刚好指向待删除结点的前一个结点。 由于head可能被删除,返回dummyHead.next一定为删除后的头结点
var removeNthFromEnd = function ( head, n ) {
let dummyHead = new ListNode ( 0 , head) ;
let slow = dummyHead;
let fast = dummyHead;
for ( let i= 0 ; fast. next != null ; i++ ) {
fast = fast. next;
if ( i >= n) {
slow = slow. next;
}
}
slow. next = slow. next. next;
return dummyHead. next;
} ;
字符串
151.翻转字符串里的单词
链接:https://leetcode.cn/problems/reverse-words-in-a-string/ 解法1:直接使用JS自带方法 trim()删除字符串的前导空格和尾随空格,split(/\s+/) 使用正则表达式(\s+:一个或多个空白字符)将字符串分割为单词数组,.reverse()反转单词数组,.join(’ ')将单词数组转化为空格分隔的字符串。
var reverseWords = function ( s ) {
return s. trim ( ) . split ( / \s+ / ) . reverse ( ) . join ( ' ' ) ;
} ;
解法2:原地解法,在数组上空间复杂度为O(1);但由于JS字符串不可操作,需要将字符串转为数组操作,空间复杂度为O(n)。
首先将字符串转换为数组,使用双指针法去除多余空格: 快指针fast和慢指针slow初始均指向数组开头。快指针遍历整个数组,当快指针指向非空格时即为一个单词的开始; 如果此时慢指针没有指向开头,慢指针手动为当前位置添加一个空格分隔单词; 之后快指针将指向的字符赋值给慢指针的位置,直到再次遇到空格,一个单词结束。 最后修改字符串长度为慢指针的值(因为慢指针每次移动后都要加1,最后的指向比字符串多一位) 然后反转数组,此时字母顺序也一并反转,需要将每个单词内的字母反转回来: 写一个可以反转数组子区间的函数reverse,使用双指针分别指向数组的开始和末尾,两个指针同时向中间移动,并交换元素。 在主函数中遍历数组,使用start标记单词起始位置,遇到空格时,反转[start,i-1]的子区间,start更新为i+1。为了反转最后一个单词,i <=数组长度,并且在i=length时也调用反转函数。 使用.join(‘’)将处理好的数组拼接为字符串,数组元素中已经有空格,分隔符为空。
var removeExtraSpaces = function ( s ) {
let slow = 0 ;
for ( let fast = 0 ; fast < s. length; fast++ ) {
if ( s[ fast] !== ' ' ) {
if ( slow !== 0 ) {
s[ slow] = ' ' ;
slow ++ ;
}
while ( fast < s. length && s[ fast] !== ' ' ) {
s[ slow ++ ] = s[ fast ++ ] ;
}
}
}
s. length = slow;
}
var reverse = function ( sArr, start, end ) {
while ( start < end) {
let temp = sArr[ start] ;
sArr[ start] = sArr[ end] ;
sArr[ end] = temp;
start ++ ;
end -- ;
}
}
var reverseWords = function ( s ) {
const sArr = Array. from ( s) ;
removeExtraSpaces ( sArr) ;
reverse ( sArr, 0 , sArr. length- 1 ) ;
let start = 0 ;
for ( let i= 0 ; i <= sArr. length; i++ ) {
if ( sArr[ i] == ' ' || i === sArr. length) {
reverse ( sArr, start, i- 1 ) ;
start = i + 1 ;
}
}
return sArr. join ( '' ) ;
} ;
右旋字符串
链接:https://kamacoder.com/problempage.php?pid=1065 给定一个字符串 s 和一个正整数 k,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。 将字符串分为两段,第一段长length-k,第二段长k,直接倒转整个字符串,可以将后k个字符移到前length-k个字符之前。 然后将两个子串再次反转,回到正序。(需要写一个可以反转数组子区间的函数reverse)
var reverse = function ( sArr, start, end ) {
while ( start < end) {
let temp = sArr[ start] ;
sArr[ start] = sArr[ end] ;
sArr[ end] = temp;
start ++ ;
end -- ;
}
}
var rotate = function ( s, k ) {
let arr = Array. from ( s) ;
let len = arr. length;
reverse ( arr, 0 , len - 1 ) ;
reverse ( arr, 0 , k - 1 ) ;
reverse ( arr, k, len - 1 ) ;
return arr. join ( '' ) ;
}
KMP算法:在字符串中查找模式串
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/ 前缀:包含首字母,不包含尾字母的所有子串 后缀:包含尾字母,不包含首字母的所有子串 前缀表:模式串 pattern[0…i] 的最长相等前后缀的长度;每个位置的数字表示,到目前为止,最长的相同的前缀和后缀序列有多长。 例:字符串aabaabaaf,模式串aabaaf,子串a最长相等前后缀长度为0, aa为1, aab为0, aaba为1, aabaa为2, aabaaf为0, 前缀表为[0, 1, 0, 1, 2, 0] 发现子串中 f 与 b 不匹配时,找到前缀表中 f 前一位的最长相等前后缀为2,由此可知字符串中b的前两位与模式串开头的两位相同,因此可以直接从模式串的第三位(索引为2)开始匹配。 总是先判断不匹配的情况,防止在匹配后再次进入while循环
求前缀表
查找对称串,如果当前字符的前一位对称程度为 j ,则说明当前字符向前数x位与字符串开头的0–(j -1)位相等(substr[0, j-1]与substr[i-j+1, i]总是相等),如果当前字符与字符串的第 j 位也相等,当前位的对称程度为 j 的基础上加1:if (s[i] === s[j]) j++;
如果当前字符与字符串的第 j 位不相等,当前对称程度必然小于上一位,因此要从字符串的前 j-1 位中寻找可能的对称。j 要根据确定的next数组值进行回溯
const getNext = function ( s ) {
let j = 0 ;
let next = new Array ( s. length) . fill ( 0 ) ;
for ( let i = 1 ; i < s. length; i++ ) {
while ( s[ j] !== s[ i] && j > 0 ) {
j = next[ j - 1 ] ;
}
if ( s[ i] === s[ j] ) {
j++ ;
}
next[ i] = j;
}
return next;
} ;
const strStr = function ( str, substr ) {
let next = getNext ( substr) ;
let j = 0 ;
for ( let i = 0 ; i < str. length; i++ ) {
while ( str[ i] !== substr[ j] && j > 0 ) {
j = next[ j - 1 ] ;
}
if ( str[ i] === substr[ j] ) {
j++ ;
}
if ( j === substr. length) {
return i - substr. length + 1 ;
}
}
return - 1 ;
} ;
哈希表
两数之和
链接:https://leetcode.cn/problems/two-sum/ 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 使用Map存储遍历过的元素,key存放元素,value存放索引。遍历某个元素时,易得和为target需要的另一个数,查找map中是否存在这个数。因此,将元素值作为Map的key。
var twoSum = function ( nums, target ) {
let numMap = new Map ( ) ;
for ( let i = 0 ; i < nums. length; i++ ) {
let key = target - nums[ i] ;
if ( numMap. has ( key) ) {
return [ numMap. get ( key) , i] ;
} else {
numMap. set ( nums[ i] , i) ;
}
}
return null ;
} ;
三数之和
链接:https://leetcode.cn/problems/3sum/ 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。答案中不可以包含重复的三元组。 双指针法,首先将数组排序,寻找三元组时,定义left为i+1,right在数组末尾。 对于每个i,由于数组有序,如果当前三元组大于0,right左移,如果小于0,left右移,直到left = right或找到三元组。
var threeSum = function ( nums ) {
nums. sort ( function ( a, b ) { return a - b; } ) ;
res = [ ] ;
for ( let i = 0 ; i < nums. length; i++ ) {
if ( nums[ i] > 0 ) return res;
if ( i >= 0 && nums[ i] === nums[ i- 1 ] ) continue ;
let left = i + 1 ;
let right = nums. length - 1 ;
while ( left < right) {
if ( nums[ i] + nums[ left] + nums[ right] > 0 ) {
right -- ;
} else if ( nums[ i] + nums[ left] + nums[ right] < 0 ) {
left ++ ;
} else {
res. push ( [ nums[ i] , nums[ left] , nums[ right] ] ) ;
while ( right > left && nums[ right] === nums[ right- 1 ] ) {
right -- ;
}
while ( right > left && nums[ left] === nums[ left+ 1 ] ) {
left ++ ;
}
right -- ;
left ++ ;
}
}
}
return res;
} ;