大厂前端面试高频算法训练——链表,字符串,数组(JS版)【更新中】

一,链表

1. 前序遍历判断回文链表 (反转,快慢指针)

传送门:【LeetCode 直通车】:234 回文链表(简单)

题解:个人认为链表的简单题比其他简单题要难,实现起来边界细节处理容易出错或者脑子转不过弯。

  • 总体思路:将链表前一半(向下取整)反转,然后从中间节点和头节点同步遍历链表前半段和后半段,每个位置判断值是否相同。

  • 反转:先定义空指针,再从中间节点处的下一个节点遍历链表至结尾,每一个节点利用头插法进行链表反转,时间复杂度O(n/2),空间复杂度O(1)。

  • 找中间节点:定义fast和slow两个指针,slow走一步,fast走两步,需要注意的是循环条件fast的下一个和下下一个都不能为空

  • 总体时间复杂度O(n),空间复杂度O(1)

 //头插法
 const reverseList=(head)=>{
     //head为要插入的
     let pre=null;
     let curr=head;
     while(curr!=null){
         let nextTemp=curr.next;
         curr.next=pre;
         pre=curr;
         curr=nextTemp;
     }
     return pre;
 }
 //找到中间节点
const findMiddle=(head)=>{
    let fast=head;
    let slow=head;
    while(fast.next!==null&&fast.next.next!==null){
        fast=fast.next.next;
        slow=slow.next;
    }
    return slow;
}
//判断
var isPalindrome = function(head) {
    if(head==null) return true;
    let mid=findMiddle(head);
    mid=mid.next;
    let reversedList=reverseList(mid);
    let p=head;
    while(p.next!=mid){
        p=p.next;
    }
    p.next=reversedList;
    p=p.next;
    while(p!=null){
        if(head.val==p.val){
            head=head.next;
            p=p.next;
        }else{
            return false;
        }
    }
    return true;
};

2. 反转链表 (头插法)
传送门:【LeetCode 直通车】:206 反转链表(简单)

解题思路:经典头插法,先定义空指针,在定义一个指针从头节点遍历

  • 时间复杂度O(n),空间复杂度O(1)

var reverseList = function(head) {
    let pre=null;
    let curr=head;
    while(curr!=null){
        let currNext=curr.next;
        curr.next=pre;
        pre=curr;
        curr=currNext;
    }
    return pre;
};

3. 合并K个升序链表 (双指针,DFS)
传送门: 【LeetCode 直通车】:23 合并K个升序链表(困难)
解题思路: 先写mergeList函数两两合并,再利用DFS
  • mergeList函数:用两个指针分别指k1,k2两个子链表。小指针前进并将值存储至list数组,将合并后的结果返回,即list数组。

  • 主函数:主函数中用DFS对每个子链表进行两两合并。

const mergeList=(k1,k2)=>{
        let list=[];
        let i=0;
        let j=0,k=0;
        while(j<k1.length&&k<k2.length){
            if(k1[j]<k2[k]){
                list[i]=k1[j];
                j++;
                i++;
            }else{
                list[i]=k2[k];
                k++;
                i++
            }
        }
        while(k<k2.length){
            list[i]=k2[k];
            i++;
            k++;
        }
        while(j<k1.length){
            list[i]=k1[j];
            i++;
            j++;
        }
        return list;
}
var mergeKLists = function(lists) {
    function dfs(i,j){
         const m=j-i;
         if(m===0) return null;
         if(m===1) return lists[i];
         const left=dfs(i,i+m/2);
         const right=dfs(i+m/2+1,j);
         return mergeList(left,right);
     }
     return dfs(0,lists.length);
};

4. K个一组翻转链表 (头插法)
传送门:【LeetCode 直通车】:25 K 个一组翻转链表(困难)
解题思路: 先写myReverse函数进行子链表翻转,主函数中遍历链表,对每段子链表进行处理
  • 链表反转函数:老生常谈,核心代码是链表的头插法,需要注意的是,此函数中顺便处理了子链表尾部的连接,此处定义一个指针connect来进行尾部连接。

  • 主函数:主函数中需要处理子链表首尾连接的关键指针是pre和af,分别指向头节点的上一个和尾节点的下一个,为对每个子数组统一处理,需要再链表最开头新建一个节点。(ps:此处关于af的指针可以去掉,因为链表反转函数顺便处理了子链表尾部的连接)

const myReverse = (head, tail) => {
    let prev = tail.next;
    let p = head;
    while (prev !== tail) {
        const nex = p.next;
        p.next = prev;
        prev = p;
        p = nex;
    }
    return [tail, head];
}
var reverseKGroup = function(head, k) {
    const hair = new ListNode(0);
    hair.next = head;
    let pre = hair;
​
    while (head) {
        let tail = pre;
        // 查看剩余部分长度是否大于等于 k
        for (let i = 0; i < k; ++i) {
            tail = tail.next;
            if (!tail) {
                return hair.next;
            }
        }
        const af = tail.next;
        [head, tail] = myReverse(head, tail);
        // 把子链表重新接回原链表
        pre.next = head;
        tail.next = af;
        pre = tail;
        head = tail.next;
    }
    return hair.next;
};
​

5. 环形链表 (快慢指针)
传送门: 【LeetCode 直通车】:141 环形链表(简单)

解题思路: 快慢指针,需要注意的是边界条件,一个节点和两个节点肯定无环,故fast和fast.next不能为空

var hasCycle = function(head) {
    let slow=head,fast=head;
    while(fast!=null&&fast.next!=null){
        fast=fast.next.next;
        slow=slow.next;
        if(fast==slow){
            return true;
        }
    }
    return false;
};

6. 排序链表 (归并排序)
传送门:【LeetCode 直通车】:148 排序链表(中等)

解题思路:

const merge=(head1,head2)=>{
    const dummyHead=new ListNode(0);
    let temp=dummyHead,temp1=head1,temp2=head2;
    while(temp1!==null&&temp2!==null){
        if (temp1.val <= temp2.val) {
            temp.next = temp1;
            temp1 = temp1.next;
        } else {
            temp.next = temp2;
            temp2 = temp2.next;
        }
        temp = temp.next;
    }
    if (temp1 !== null) {
        temp.next = temp1;
    } else if (temp2 !== null) {
        temp.next = temp2;
    }
    return dummyHead.next;
 }
​
const toSortList = (head,tail) => {
    if(head===null) {
        return head;
    }
    if(head.next===tail) {
        head.next=null;
        return head;
    }
    let slow=head,fast=head;
    while(fast!==tail) {
        slow=slow.next;
        fast=fast.next;
        if(fast!=tail) {
            fast=fast.next;
        }
    }
    const mid=slow;
    return merge(toSortList(head,mid),toSortList(mid,tail));
}
var sortList = function(head) {
    return toSortList(head,null);
};

7. 相交链表
传送门:【LeetCode 直通车】:160 相交链表(简单)

解题思路: 较长的链表的遍历指针先行两指针之差步,然后两指针同步向前,若两指针所指节点相同,返回该节点。

var getIntersectionNode = function(headA, headB) {
    let lena=0,lenb=0;
    let p1=headA,p2=headB;
    let long,short;
    while(p1!=null){
        lena++;
        p1=p1.next;
    }
    while(p2!=null){
        lenb++;
        p2=p2.next;
    }
    lena>lenb?(long=headA,short=headB):(long=headB,short=headA);
    let diff=Math.abs(lena-lenb);
    for(let i=1;i<=diff;i++){
        long=long.next;
    }
    let tag=null;
    while(short){
        if(short==long){
            tag=short;
            return tag;
        }
        short=short.next;
        long=long.next;
    }
    return tag;
};

二,字符串

1. 最长回文子串 (动态规划)
传送门:【LeetCode 直通车】:5 最长回文子串(中等)

解题思路: 此题动态规划可解,试想一个回文串的子串肯定也是一个回文串,我们可以根据这个建立动态规划方程:当前状态=上一个状态&上一状态子串左边和右边的字符相同

var longestPalindrome = function(s) {
    let len=s.length;
    let res='';
    let dp=Array.from(new Array(len),()=>new Array(len).fill(false));
    for(let i=len-1;i>=0;i--){
        for(let j=i;j<len;j++){
            dp[i][j]=s[i]==s[j]&&(j-i<2||dp[i+1][j-1]);
            if(dp[i][j]&&j-i+1>res.length){
                res=s.substring(i,j+1);
            }
        }
    }
    return res;
};

2. 最长公共前缀
传送门:14 最长公共前缀(简单)

解题思路: 初始化一个字符串,维护这个字符串始终为每一步的最短前缀

  • 时间复杂度O(n*max(strs.length)),空间复杂度O(max(strs.length))

var longestCommonPrefix = function(strs) {
    if(strs.length==0){
        return "";
    }
    let ans=strs[0];
    for(let i=0;i<strs.length;i++){
        for(var j=0;j<Math.min(strs[i].length,ans.length);j++){
            if(ans[j]!=strs[i][j]){
                break;
            }
​
        }
        ans=ans.substr(0,j)
    }
    return ans;
};

3. 无重复字符的最长子串 (动态规划)
传送门:【LeetCode 直通车】:3 无重复字符的最长子串(中等)

解题思路:动态规划,无重复字符串的子字符串肯定也无重复,所以状态转移方程为:当前状态=上一个状态&当前字符是否已存在于子串中

  • Tn:O(n*n),Sn:O(n) 。ps:这个题跑出来的时间和空间都不太好

var lengthOfLongestSubstring = function(s) {
    let n=s.length;
    let len=1;
    let dp=Array.from(new Array(n).fill(false));
    if(s==''){
        return 0;
    }
    for(let i=0;i<n-1;i++){
        dp[i]=true;
        for(let j=i+1;j<n;j++){
            let str=s.substring(i,j);
            dp[j]=dp[j-1]&&str.indexOf(s[j])==-1;
            if(dp[j]){
                if(j-i+1>len){
                    len=j-i+1;
                }
            }else{
                break;
            }
        }
    }
    return len;
};

4. 最小覆盖子串 (滑动窗口)
传送门:【LeetCode 直通车】:76 最小覆盖子串(困难)

解题思路:滑动窗口 ,思路是遍历字符串并维护一个窗口,t字符串的长度记为len,计数器count=0,每遍历一个字符,判断该字符是否是否属于t...

var minWindow = function(s, t) {
    let need = {}, window = {};
    for (let c of t) {
        if (!need[c]) need[c] = 1;
        else need[c]++;
    }
    let left = 0, right = 0;
    let valid = 0, len = Object.keys(need).length;
    let minLen = s.length + 1, minStr = '';
    while (right < s.length) {
        const d = s[right];
        right++;
        if (!window[d]) window[d] = 1;
        else window[d]++;
        if (need[d] && need[d] === window[d]) {
            valid++;
        }
        while (valid === len) {
            if (right - left < minLen) {
                minLen = right - left;
                minStr = s.slice(left, right);
            }
            console.lo
            let c = s[left];
            left++;
            window[c]--;
            if (need[c] && window[c] < need[c]) {
                valid--;
            }
        }
    }
    return minStr;
};

三,数组

1. 最长连续递增序列 (双指针)

传送门:【LeetCode 直通车】:674 最长连续递增序列(简单)

var findLengthOfLCIS = function(nums) {
    let ans=1;
    let i=0,j=0;
    while(j<nums.length) {
        if(i<j&&nums[j]<=nums[j-1]){
            i=j;
        }
        ans=Math.max(j-i+1,ans);
        j++;
    }
    return ans;
};

2. 乘最多水的容器

传送门:【LeetCode 直通车】:11 盛最多水的容器(中等)

var maxArea = function(height) {
    let area=0;
    for(let i=0,j=height.length-1;i<j;){
        area=Math.max(area,(j-i)*Math.min(height[i],height[j]));
        height[i]>height[j]?j--:i++;
    }
    return area;
};

3. 删除有序数组中的重复项

传送门:【LeetCode 直通车】:26 删除有序数组中的重复项(简单)

var removeDuplicates = function(nums) {
    let i=0;j=1;
    if(nums.length==0){
        return 0;
    }
    while(j<nums.length){
        if(nums[j]!==nums[j-1]){
            i++;
            nums[i]=nums[j];
        }
        j++;
    }
    return i+1;
};

4. 和为K的子数组

传送门:【LeetCode 直通车】:560 和为K的子数组(中等)

var subarraySum = function(nums, k) {
    let count=0,sum=0;
    let map=new Map();
    map.set(0,1);
    for(let i=0;i<nums.length;++i){
        sum=sum+nums[i];
        if(map.has(sum-k)){
            count+=map.get(sum-k);
        }
        if(map.has(sum)){
            map.set(sum,map.get(sum)+1);
        }else{
            map.set(sum,1);
        }
    }
    return count;
};

5. nSum问题

传送门: 【LeetCode 直通车】:1 两数之和(简单)

【LeetCode 直通车】:167 两数之和 II - 输入有序数组(简单)

【LeetCode 直通车】:15 三数之和(中等)

【LeetCode 直通车】:18 四数之和(中等)

var twoSum = function(nums, target) {
    let idx=new Map();
    for(let j=0;;j++){
        const x=nums[j];
        if(idx.has(target-x)) return [idx.get(target-x),j];
        idx.set(x,j);
    }
};
var twoSum = function(numbers, target) {
    let left=0,right=numbers.length-1;
    while(left<right){
        if(numbers[left]+numbers[right]===target) return [++left,++right];
        else if(numbers[left]+numbers[right]>target) right--;
        else left++;
    }
};
var threeSum = function(nums) {
    nums.sort((a,b)=>a-b);
    const n=nums.length;
    let ans=[];
    for(let i=0;i<n-2;i++){
        if(i>0&&nums[i]===nums[i-1]){
            continue;
        }
        if(nums[i]+nums[i+1]+nums[i+2]>0) break;
        if(nums[i]+nums[n-1]+nums[n-2]<0) continue;
        let j=i+1,k=n-1;
        while(j<k){
            let temp=nums[j]+nums[k]+nums[i];
            if(temp>0) k--;
            else if(temp<0) j++;
            else{
                ans.push([nums[i],nums[j],nums[k]]);
                j++;
                while(j<k&&nums[j]===nums[j-1]) j++;
                k--;
                while(k>k&&nums[k]===nums[k+1]) k--;
            }
        }
    }  
    return ans;  
};
var fourSum = function(nums, target) {
    nums.sort((a,b)=>a-b);
    let n=nums.length;
    let ans=[];
    if(n<4) return ans;
    for(let i=0;i<n-3;i++){
        if(i>0&&nums[i]===nums[i-1]){
            continue;
        }
        if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target) break;
        if(nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue;
        for(let j=i+1;j<n-2;j++){
            if(j>i+1&&nums[j]===nums[j-1]){
            continue; 
            }
            if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target) break;
            if(nums[i]+nums[j]+nums[n-1]+nums[n-2]<target) continue;
            let k=j+1,p=n-1;
            while(k<p){
                let sum=nums[i]+nums[j]+nums[k]+nums[p];
                if(sum>target) p--;
                else if(sum<target) k++;
                else{
                    ans.push([nums[i],nums[j],nums[k],nums[p]]);
                    k++;
                    while(k<p&&nums[k-1]===nums[k]) k++;
                    p--;
                    while(k<p&&nums[p]===nums[p+1]) p--;
                }
            }
            
        }
    }
    return ans;
};

6. 跳跃游戏(贪心算法)

传送门:【LeetCode 直通车】:55 跳跃游戏(中等)

var subarraySum = function(nums, k) {
    let count=0,sum=0;
    let map=new Map();
    map.set(0,1);
    for(let i=0;i<nums.length;++i){
        sum=sum+nums[i];
        if(map.has(sum-k)){
            count+=map.get(sum-k);
        }
        if(map.has(sum)){
            map.set(sum,map.get(sum)+1);
        }else{
            map.set(sum,1);
        }
    }
    return count;
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值