Leetcode刷题记录(js)

数组

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]){
        //因为nums[middle]!=target,下一步查找要将边界加1或减1
            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));
  1. 如果n为奇数,矩阵有Math.floor(n/2)圈,并将中间元素填充为n^2
  2. 如果n为偶数,矩阵有Math.floor(n/2)圈
  3. 每一圈设为一个循环,在循环内分别填充四条边,左闭右开
  4. 设置startx,starty为每一圈起始坐标(左上角),每圈加一
  5. 设置n - offset为每条边向右/向下的终止位置,由于左闭右开,offset初始为1,每圈加一
  6. 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/
  • 设计并实现一个链表
  1. 实现节点类和链表对象,节点类使用值和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;
};
  1. 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;
};
  1. 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;
    }
};
  1. 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;
    }
};
  1. 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 ++;
};
  1. 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
  1. 双指针法:将每个结点的next指针指向前一个结点。定义cur指向头结点,pre指向前一个结点,temp在cur改变指向后暂存下一个结点。
var reverseList = function(head) {
    let pre = null, temp = null;
    let cur = head;
    while(cur){
        temp = cur.next;// temp暂存下一个结点
        cur.next = pre;// 当前结点的next指针指向前一个结点
        pre = cur;// 前一个结点后移
        cur = temp;// 当前结点后移
    }
    return pre;
};
  1. 递归法
 var reverse = function(pre, cur){
    if(!cur) return pre;
    let temp = cur.next;
    cur.next = pre;// 反转指针指向
    return reverse(cur, temp);// 相当于pre和cur分别后移一位
 }
var reverseList = function(head) {
    return reverse(null, head); // 初始pre和cur分别指向null和头结点
};

反转奇数位置的链表

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/
  1. 设置快指针fast每次走两个结点,慢指针slow每次走一个结点。如果快慢指针不会相遇,则链表无环。
  2. 头结点到环入口距离为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。
  3. 根据上式,由于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每次走两个结点(检查两个null),慢指针slow每次走一个结点
        fast = fast.next.next;
        slow = slow.next;
        if(fast === slow){
        	// 快慢指针相遇时,重新设置一个指针从head出发,一个从相遇结点出发
            slow = head;
            while(fast !== slow){
                fast = fast.next;
                slow = slow.next;
            }
            return slow;// 相遇位置即为环入口
        }
    }
    return null;// 没有环返回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)。
  1. 首先将字符串转换为数组,使用双指针法去除多余空格:
    快指针fast和慢指针slow初始均指向数组开头。快指针遍历整个数组,当快指针指向非空格时即为一个单词的开始;
    如果此时慢指针没有指向开头,慢指针手动为当前位置添加一个空格分隔单词;
    之后快指针将指向的字符赋值给慢指针的位置,直到再次遇到空格,一个单词结束。
    最后修改字符串长度为慢指针的值(因为慢指针每次移动后都要加1,最后的指向比字符串多一位)
  2. 然后反转数组,此时字母顺序也一并反转,需要将每个单词内的字母反转回来:
    写一个可以反转数组子区间的函数reverse,使用双指针分别指向数组的开始和末尾,两个指针同时向中间移动,并交换元素。
    在主函数中遍历数组,使用start标记单词起始位置,遇到空格时,反转[start,i-1]的子区间,start更新为i+1。为了反转最后一个单词,i <=数组长度,并且在i=length时也调用反转函数。
  3. 使用.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; //j代表子串[0...i]最长相等前后缀的长度
        //i指向子串后缀末尾
        let next = new Array(s.length).fill(0);
        for (let i = 1; i < s.length; i++) { 
        //next[0]总是为0,所以i从1开始
        //前缀末尾和后缀末尾不匹配,前缀位置回退
            while (s[j] !== s[i] && j > 0) {
                j = next[j - 1];
            }
        //当前缀末尾字符和后缀末尾字符匹配时,前缀长度加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]]);
                //去重,如果right指向和前一个数相等或者left指向和后一个数相等,
                //跳过这些数直到不再重复
                while(right > left && nums[right] === nums[right-1]){
                    right --;
                }
                while(right > left && nums[left] === nums[left+1]){
                    left ++;
                }
                right --;
                left ++;
            }
        }
    }
    return res;
};
  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值