从零开始学算法的第三天~(删除链表的倒数第 N 个结点,无重复字符的最长子串,字符串的排列)

前言

今天就是我 硬钢算法的第三天,我的基础真的很差。文章重在记录总结,我也很好奇我会坚持多久,我只有以写文章的形式记录每天刷的题才能让自己保持一个积极的态度,不然每次看一会就懈怠了😭。只有看到大家的反馈我才能源源不断的坚持下去!

过往文章:

# 👨‍💻从零开始学算法的第一天~(二分查找,双指针,两数相加,最长回文子串)

# 👨‍💻从零开始学算法的第二天~(最长公共前缀,有效的括号,移动零)

刷题

19. 删除链表的倒数第 N 个结点

这题我觉得对我的提升很大,尤其是对快慢指针和链表的一些操作都有帮助。

删除链表的倒数第N个节点,难度就在于链表结构是不能直接通过索引去找到位置的,不像是数组,而且我们也不知道 链表的长度

最简单的解法是先遍历一次链表获取链表的长度,然后再根据长度找 倒数第N的节点 给返回出去。但是这里使用的是一个快慢指针的方法,我们不需要知道链表的长度就能解决问题。

定义两个指针,让前面的指针先跑N步,另一个指针不动,然后遍历链表,当 先跑N步的指针跑到链表末尾 的时候,后出发的那个指针不久正好在 倒数第N个节点 上嘛~

var removeNthFromEnd = function(head, n) {
    let fast,slow
    fast = slow = head
    
    while(n--){
        fast = fast.next
    }
    if(!fast){
        return head.next
    }
    while(fast.next){
        fast = fast.next
        slow = slow.next
    }
    slow.next = slow.next.next
    return head
};

3. 无重复字符的最长子串

这一题的解法是通过一个滑动窗口来不断的 判断窗口内子串的值有没有重复,如果没有重复就加大窗口,如果重复了就移动窗口。这么说可能比较抽象。大家可以点这个链接看动画 https://www.bilibili.com/video/BV14Q4y1P7Np
讲的特别形象直观

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    if(!s.length){
        return 0
    }
    let left=0,right=1
    let maxLength = 0
    while(right<=s.length){
        const nowStr = s.substring(left,right)
        const index = nowStr.indexOf(s[right])
        if(index !== -1){
            left +=  index+1 
        }
        maxLength = Math.max(maxLength,nowStr.length)
        right ++
    }
    return maxLength
};

这一题今天真是把我折磨坏了,从第一次提交代码就能通过差不多一半测试用例,后面折腾了好久才跑通过,就是不看题解,硬要自己解出来,晚上回家解出来后舒服多了哈哈哈。

image.png

567. 字符串的排列

这一题的话我也是自己硬琢磨,因为 子串 不需要考虑排序,只需要考虑有什么值以及这个值出现了几次,于是我选择了用一个 map 也就是对象先将短的字符串遍历后存储起来,这样我们就知道有什么字符且出现了几次。

然后我们再遍历长的字符串,这里我是固定了一个 长度为短字符串长度 范围,然后每一次再判断这个范围内 截出来的字符串如果转化为map 是否与 短字符串的map 相同。而对象的话因为是引用类型,直接对比的话是不行的,因为内存位置不同,当然我们可以采取 JSON.stringify 的方式转化后对比 json字符串。但是这样会浪费很多性能。

因此我采取了一个借钱还钱的策略,短字符串的map 中的值就是借的钱,我们再遍历 截出来的字符串 时就是一个还钱的操作,遍历到相同的属性就让它的值减一,模拟一个还钱的过程。如果为零就删除,最终判断 对象的属性数量 是否0,如果为0说明钱还完了。

/**
 * @param {string} s1
 * @param {string} s2
 * @return {boolean}
 */
var checkInclusion = function(s1, s2) {
    if(s1.length>s2.length){
        return false
    }
    const map = {}
   for(let item of s1){
       if(map[item]){
           map[item]++
       }
       else{
           map[item] = 1
       }
   }
    let left = 0
    let right = s1.length
    while(right <= s2.length){
        let tempMap = {...map}
        const curStr = s2.substring(left,right)
        for(let item of curStr){
            if(tempMap[item]>1){
                tempMap[item] -=1
            }
            else if(tempMap[item]===1){
                delete tempMap[item]
            }
        }
        if(!Object.keys(tempMap).length){
            return true
        }
        right++
        left++
    }
    return false
};

我写的方法性能不太好,因此我看了一下最优解。

这里放一下最优解,不是我写的,我看了一下之后发现实现的思路与我的是差不多的。只是最优解并不是用一个map来存储欠的钱,而是使用一个数字数组,长度为26,索引分别对应二十六个字母的位置,而值就是某个字母的出现次数,后面也就是一个 遍历还钱 的过程拉。

var checkInclusion = function(s1, s2) {
    // 自定义方法,用于比较滑动窗口中的数据与目标数据是否相同
    function equal(a,b){
        for(let i =0;i<a.length;i++){
            if(a[i]!==b[i])return false
        }
        return true
    }

    let arrP=new Array(26).fill(0),
    arr=new Array(26).fill(0)    

    // 将目标数据放入数组,之后使用该数组与滑动窗口进行对比
    for(let i =0;i<s1.length;i++) arrP[s1.charCodeAt(i)-'a'.charCodeAt()]++

    // 遍历整个长字符串,在字符串内部使用滑动窗口逐一访问判断
    for(let i =0;i<s2.length;i++){

        // 创建滑动窗口数组,滑动窗口数组的长度应与目标数组相同
        arr[s2.charCodeAt(i)-'a'.charCodeAt()]++

        console.log(s2.charCodeAt(i))
        // 滑动窗口创建完成之前不进行任何操作,长度不同不可能与目标数组相符
        if(i>=s1.length-1) {

            // 滑动窗口创建完毕之后即刻开始与目标窗口进行比对,比对一致即返回true
            if(equal(arr,arrP)) return true
            
            else{
            // 比对不一致,则滑动窗口向右滑动,最左边字符退出窗口,进入下一次循坏,在下一次循环中又会将窗口外右边的第一个字符放入窗口
            arr[s2.charCodeAt(i-s1.length+1)-'a'.charCodeAt()]--
            }
        }
    }
    // 比对完成都没有发现匹配数据,则说明无匹配数据,返回false
    return false
};

这里讲一下 charCodeAt 这个方法是用于返回字符串第一个字符的 Unicode,这一步操作呢可以帮助我们将字母转换为数字进行存储。

我们可以看到很多 xxx - 'a'.charCodeAt() 的操作,这一步其实就是假设我们的 a-z 代表了 10-35,那么我们想要让 a-z 代表 0-25 的话我们只需要每 一个值都减10 就好了。而这里的'a'.charCodeAt() 在转换为数字后你可以理解为这个 10 的值,只是为了更方便的当作 数组的索引 使用

总结

这几天工作好累,刷题时间少了很多,而且题目的难度也在慢慢增加,每一题我都死磕很久哈哈哈。但是整体来说有很多思想上的进步,这才三天呢 ~ 继续加油吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值